Fix both update_tree_view() and traverseUpdate_tree()
[kmk.git] / src / kmk.cpp
blobd92c6fa0dba11976b2cc18331b087950dd271b55
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 catalogUpdateSubtreeAction = new KAction(i18n("Update catalog subtree "), KShortcut(""), this,
131 SLOT(slotCatalogUpdateSubtree()), actionCollection(), "update_subtree");
133 // File menu actions
134 fileNewAction->plug( file );
135 fileOpenAction->plug( file );
136 // catalogAddNewFolderAction->setEnabled( FALSE );
137 catalogAddNewFolderAction->plug( file );
138 fileSaveAction->setEnabled( FALSE );
139 fileSaveAction->plug( file );
140 fileSaveAsAction->setEnabled( FALSE );
141 fileSaveAsAction->plug( file );
142 fileCatalogFileStats->setEnabled( FALSE );
143 fileCatalogFileStats->plug( file );
144 fileCatalogFileClose->setEnabled( FALSE );
145 fileCatalogFileClose->plug( file );
146 file->insertSeparator();
147 fileQuitAction->plug( file );
148 menuBar()->insertItem( i18n("Catalog"), file );
149 // Tools menu actions
150 file = new KPopupMenu( this );
151 TagEditAction->plug( file );
152 menuBar()->insertItem( i18n("Tools"), file );
153 // Player controls menu actions
154 file = new KPopupMenu( this );
155 playerPreviousAction->plug( file );
156 playerPlayAction->plug( file );
157 playerPauseAction->plug( file );
158 playerStopAction->plug( file );
159 playerNextAction->plug( file );
160 menuBar()->insertItem( i18n("Player controls"), file );
161 // Settings menu actions
162 file = new KPopupMenu( this );
163 programSettingsAction->plug( file );
164 // listTableToggleTitleAction->plug( file );
165 menuBar()->insertItem( i18n("Settings"), file );
166 // Help menu actions
167 file = helpMenu(); menuBar()->insertItem( i18n("Help"), file );
169 // Toolbar menu
170 KToolBar * fileToolsToolbar = new KToolBar( this, "File operations" );
171 fileToolsToolbar->setOrientation( Qt::Horizontal );
172 fileToolsToolbar->setIconText( KToolBar::IconOnly );
173 fileToolsToolbar->setLabel( i18n("File operations") );
175 fileNewAction->plug( fileToolsToolbar );
176 fileOpenAction->plug( fileToolsToolbar );
177 fileSaveAction->plug( fileToolsToolbar );
178 fileToolsToolbar->insertSeparator();
179 fileQuitAction->plug( fileToolsToolbar );
181 KToolBar* playerActionsToolbar = new KToolBar( this, "Player controls" );
182 playerActionsToolbar->setOrientation( Qt::Horizontal );
183 playerActionsToolbar->setIconText( KToolBar::TextOnly );
184 playerActionsToolbar->setLabel( i18n("Player controls") );
185 playerPreviousAction->plug( playerActionsToolbar );
186 playerPlayAction->plug( playerActionsToolbar );
187 playerPauseAction->plug( playerActionsToolbar );
188 playerStopAction->plug( playerActionsToolbar );
189 playerNextAction->plug( playerActionsToolbar );
191 moveDockWindow( fileToolsToolbar, Top );
192 moveDockWindow( playerActionsToolbar, Top );
194 // popup menus
195 CTree_PopupMenu = new QPopupMenu( this, "catalog tree popup" );
196 fileNewAction->plug( CTree_PopupMenu );
197 catalogAddNewFolderAction->plug( CTree_PopupMenu );
198 CTree_PopupMenu->insertSeparator(2);
199 playerPlayDirAction->plug( CTree_PopupMenu );
200 playerPlayDirSubdirsAction->plug( CTree_PopupMenu );
201 CTree_PopupMenu->insertSeparator(5);
202 playerEnqueueDirAction->plug( CTree_PopupMenu );
203 playerEnqueueDirSubdirsAction->plug( CTree_PopupMenu );
204 CTree_PopupMenu->insertSeparator(8);
205 catalogUpdateSubtreeAction->plug( CTree_PopupMenu );
207 CList_PopupMenu = new QPopupMenu( this, "files list popup" );
208 playerPlaySelectionAction->plug( CList_PopupMenu );
209 playerPlaySelectionAction->setEnabled( FALSE );
210 playerEnqueueAction->plug( CList_PopupMenu );
211 playerEnqueueAction->setEnabled( FALSE );
212 CList_PopupMenu->insertSeparator(2);
213 TagEditAction->plug( CList_PopupMenu );
214 TagEditAction->setEnabled( FALSE );
216 CSearchList_PopupMenu = new QPopupMenu( this, "search list popup" );
217 playerPlaySelectionAction->plug( CSearchList_PopupMenu );
218 playerEnqueueAction->plug( CSearchList_PopupMenu );
219 CSearchList_PopupMenu->insertSeparator(2);
220 TagEditAction->plug( CSearchList_PopupMenu );
221 listTableLocateAction->plug( CSearchList_PopupMenu );
223 if( s->dbg() & KMK_DBG_SIGNALS ) kdDebug() << "constructor: connecting signals to slots..." << endl;
225 connect( treeListView, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int ) ),
226 this, SLOT( slotTreeListViewPopupMenuRequested( QListViewItem*, const QPoint &, int ) ) );
227 connect( filesListView, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int ) ),
228 this, SLOT( slotFileListTablePopupMenuRequested( QListViewItem*, const QPoint &, int ) ) );
229 connect( searchFilesListView, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int ) ),
230 this, SLOT( slotSearchListTablePopupMenuRequested( QListViewItem*, const QPoint &, int ) ) );
231 connect( treeListView, SIGNAL( currentChanged( QListViewItem* ) ),
232 this, SLOT( slotTreeListViewCurrentChanged( QListViewItem* ) ) );
233 connect( leSearchFor, SIGNAL( returnPressed() ),
234 this, SLOT( slotSearchButtonClicked() ) );
235 connect( pbSearch, SIGNAL( clicked() ),
236 this, SLOT( slotSearchButtonClicked() ) );
237 connect( pbClear, SIGNAL( clicked() ),
238 this, SLOT( slotClearButtonClicked() ) );
239 connect( pbDone, SIGNAL( clicked() ),
240 this, SLOT( slotDoneButtonClicked() ) );
242 if( s->dbg() & KMK_DBG_SIGNALS ) kdDebug() << "constructor: done!" << endl;
244 FileSaveCalledFromFileSaveAs = FALSE;
245 tree_needs_update = FALSE;
247 QDir tmp_dir; tmp_dir.mkdir( "/tmp/kmk", TRUE );
249 setAutoSaveSettings();
250 applyMainWindowSettings( kapp->config(), QString::fromLatin1("MainWindow") );
252 clearCatalogData();
253 if ( s->loadLast() )
255 if ( !s->lastCatalogUsed().isEmpty() )
257 show();
258 loadCatalog( s->lastCatalogUsed() );
259 update_tree_view();
264 void kmk::init_interface()
266 main_container_widget = new QWidget( this, "main_container_widget" );
268 setCentralWidget( main_container_widget );
270 kmkwidgetbaseLayout = new QVBoxLayout( main_container_widget, 11, 6, "kmkwidgetbaseLayout");
272 tabWidget1 = new QTabWidget( main_container_widget, "level1_tabwidget" );
274 tab = new QWidget( tabWidget1, "level1_tab1" );
275 tabLayout = new QVBoxLayout( tab, 11, 6, "tab1_Layout");
277 splitter1 = new QSplitter( tab, "splitter1" );
278 splitter1->setMinimumSize( QSize( 182, 60 ) );
279 splitter1->setOrientation( QSplitter::Horizontal );
281 treeListView = new kmk_tree_KListView( splitter1, "treeListView" );
282 treeListView->addColumn( i18n( "Catalog tree " ) );
283 treeListView->setResizePolicy( KListView::AutoOneFit );
284 treeListView->setShowSortIndicator( TRUE );
285 treeListView->setRootIsDecorated( TRUE );
286 treeListView->setItemsMovable( TRUE );
287 treeListView->setDragEnabled( TRUE );
288 treeListView->setDropVisualizer( TRUE );
290 filesListView = new kmk_files_KListView( splitter1, "filesListView" );
291 filesListView->addColumn( i18n( "Folder" ) );
292 filesListView->addColumn( i18n( "Filename" ) );
293 filesListView->addColumn( i18n( "Artist" ) );
294 filesListView->addColumn( i18n( "Title" ) );
295 filesListView->addColumn( i18n( "Album" ) );
296 filesListView->addColumn( i18n( "Genre" ) );
297 filesListView->addColumn( i18n( "Comment" ) );
298 filesListView->addColumn( i18n( "Year" ) );
299 filesListView->addColumn( i18n( "Track #" ) );
300 filesListView->addColumn( i18n( "Duration" ) );
301 filesListView->addColumn( i18n( "Size" ) );
302 filesListView->addColumn( i18n( "Bitrate" ) );
303 filesListView->addColumn( i18n( "Sampling rate" ) );
304 filesListView->addColumn( i18n( "Channels" ) );
305 filesListView->addColumn( i18n( "Type" ) );
306 filesListView->addColumn( i18n( "READ ONLY" ) );
307 filesListView->setResizePolicy( KListView::AutoOneFit );
308 filesListView->restoreLayout( kapp->config(), "listView" );
309 filesListView->setAllColumnsShowFocus( TRUE );
310 filesListView->setShowSortIndicator( TRUE );
311 filesListView->setRootIsDecorated( FALSE );
312 filesListView->setItemsMovable( FALSE );
313 filesListView->setSelectionMode( QListView::Extended );
314 filesListView->setDragEnabled( TRUE );
315 filesListView->setDropVisualizer( TRUE );
316 tabLayout->addWidget( splitter1 );
317 tabWidget1->insertTab( tab, i18n("Catalog") );
319 tab_2 = new QWidget( tabWidget1, "level1_tab2" );
320 tabLayout_2 = new QVBoxLayout( tab_2, 11, 6, "tab2_Layout");
322 layout5 = new QVBoxLayout( 0, 0, 6, "layout5");
324 tabWidget2 = new QTabWidget( tab_2, "level2_tabwidget" );
325 tabWidget2->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)0, (QSizePolicy::SizeType)0, 0, 0,
326 tabWidget2->sizePolicy().hasHeightForWidth() ) );
327 tabWidget2->setMinimumSize( QSize( 480, 158 ) );
329 tab_3 = new QWidget( tabWidget2, "level2_tab1" );
331 pbSearch = new QPushButton( tab_3, "pbSearch" );
332 pbSearch->setGeometry( QRect( 370, 10, 100, 26 ) );
333 pbSearch->setText( i18n( "Go!" ) );
334 pbSearch->setDefault( TRUE );
336 pbClear = new QPushButton( tab_3, "pbClear" );
337 pbClear->setGeometry( QRect( 370, 50, 100, 26 ) );
338 pbClear->setText( i18n( "Clear" ) );
340 pbDone = new QPushButton( tab_3, "pbDone" );
341 pbDone->setGeometry( QRect( 370, 90, 100, 26 ) );
342 pbDone->setText( i18n( "Done" ) );
344 frame1 = new QFrame( tab_3, "frame1" );
345 frame1->setGeometry( QRect( 10, 10, 350, 110 ) );
346 frame1->setFrameShape( QFrame::StyledPanel );
347 frame1->setFrameShadow( QFrame::Raised );
349 textLabel1 = new QLabel( frame1, "textLabel1" );
350 textLabel1->setGeometry( QRect( 10, 10, 100, 16 ) );
351 textLabel1->setText( i18n( "Look for" ) );
353 leSearchFor = new KLineEdit( frame1, "leSearchFor" );
354 leSearchFor->setGeometry( QRect( 10, 30, 330, 24 ) );
355 tabWidget2->insertTab( tab_3, i18n("Base") );
357 tab_4 = new QWidget( tabWidget2, "level2_tab2" );
358 tabWidget2->insertTab( tab_4, i18n("Advanced") );
359 layout5->addWidget( tabWidget2 );
361 searchFilesListView = new kmk_files_KListView( tab_2, "searchFilesListView" );
362 searchFilesListView->addColumn( i18n( "Folder" ) );
363 searchFilesListView->addColumn( i18n( "Filename" ) );
364 searchFilesListView->addColumn( i18n( "Artist" ) );
365 searchFilesListView->addColumn( i18n( "Title" ) );
366 searchFilesListView->addColumn( i18n( "Album" ) );
367 searchFilesListView->addColumn( i18n( "Genre" ) );
368 searchFilesListView->addColumn( i18n( "Comment" ) );
369 searchFilesListView->addColumn( i18n( "Year" ) );
370 searchFilesListView->addColumn( i18n( "Track #" ) );
371 searchFilesListView->addColumn( i18n( "Duration" ) );
372 searchFilesListView->addColumn( i18n( "Size" ) );
373 searchFilesListView->addColumn( i18n( "Bitrate" ) );
374 searchFilesListView->addColumn( i18n( "Sampling rate" ) );
375 searchFilesListView->addColumn( i18n( "Channels" ) );
376 searchFilesListView->addColumn( i18n( "Type" ) );
377 searchFilesListView->addColumn( i18n( "READ ONLY" ) );
378 searchFilesListView->setResizePolicy( KListView::AutoOneFit );
379 searchFilesListView->restoreLayout( kapp->config(), "searchListView" );
380 searchFilesListView->setAllColumnsShowFocus( TRUE );
381 searchFilesListView->setShowSortIndicator( TRUE );
382 searchFilesListView->setRootIsDecorated( FALSE );
383 searchFilesListView->setItemsMovable( FALSE );
384 searchFilesListView->setDragEnabled( TRUE );
385 searchFilesListView->setDropVisualizer( TRUE );
386 searchFilesListView->setSelectionMode( QListView::Extended );
387 layout5->addWidget( searchFilesListView );
388 tabLayout_2->addLayout( layout5 );
389 tabWidget1->insertTab( tab_2, i18n("Search") );
390 kmkwidgetbaseLayout->addWidget( tabWidget1 );
391 // resize( QSize(742, 507).expandedTo(minimumSizeHint()) );
392 // clearWState( WState_Polished );
394 // tab order
395 setTabOrder( tabWidget1, treeListView );
396 setTabOrder( treeListView, filesListView );
397 setTabOrder( filesListView, tabWidget2 );
398 setTabOrder( tabWidget2, leSearchFor );
399 setTabOrder( leSearchFor, pbSearch );
400 setTabOrder( pbSearch, pbClear );
401 setTabOrder( pbClear, pbDone );
402 setTabOrder( pbDone, searchFilesListView );
405 kmk::~kmk()
408 filesListView->saveLayout( kapp->config(), "listView" );
409 searchFilesListView->saveLayout( kapp->config(), "searchListView" );
411 if( s->dbg() & KMK_DBG_OTHER ) kdDebug() << "destructor: shutting down kmk "VERSION"..." << endl;
412 saveMainWindowSettings( kapp->config(), QString::fromLatin1("MainWindow") );
413 Q_CHECK_PTR( s );
414 switch ( CatalogState )
416 case NoCatalog:
417 s->setLastCatalogUsed( "" );
418 break;
419 case Modified:
420 case Saved:
421 s->setLastCatalogUsed( catalogFileName );
422 break;
423 default:
424 break;
426 s->saveSettings( kapp->config() );
428 kapp->config()->setGroup( "GUI" );
429 kapp->config()->writeEntry( "splitter config", splitter1->sizes() );
430 kapp->config()->writeEntry( "main win position", pos() );
432 clearCatalogData();
433 if (kmkProgress) delete ( kmkProgress );
434 if (kmkProgressTimer) delete ( kmkProgressTimer );
435 if (kmkSmooth) delete ( kmkSmooth );
436 if (player) delete ( player );
438 if( s->dbg() & KMK_DBG_OTHER ) kdDebug() << "destructor: done!" << endl;
440 delete s;
443 void kmk::slotUpdateProgressDisp()
445 if ( files_to_read )
446 kmkProgress->kPrg->setProgress( total_files );
447 /* qDebug( " setting progres to "+QString::number( (int)((total_bytes/(float)bytes_to_read)*100 )) );
448 qDebug( " need to read: "+QString::number( bytes_to_read ) );
449 qDebug( " read: "+QString::number( total_bytes ) );*/
452 void kmk::slotFileNew()
454 if( CatalogState == Modified )
455 switch( KMessageBox::questionYesNoCancel( this,
456 i18n("The catalog [%1] contains unsaved changes.\n"
457 "Do you want to save the changes?")
458 .arg(catalogFileName),
459 i18n("KDE Music Kataloger") ) ) {
460 case KMessageBox::Yes: // Save
461 slotFileSave();
462 break;
463 case KMessageBox::No: // Discard - just continue
464 break;
465 case KMessageBox::Cancel: // Cancel creating new catalog
466 return;
467 break;
469 QFileDialog* fd = new QFileDialog( this, "file dialog", TRUE );
470 fd->setCaption( i18n("Select directory to scan") );
471 fd->setMode( QFileDialog::Directory );
472 fd->setFilter( "*" );
473 /* KFileDialog * t = new KFileDialog( ":&lt;scandir&gt;", "*", this, i18n("Select directory to scan"), TRUE );
474 t->exec(); */
475 // = KFileDialog::getExistingDirectory( ":&lt;scandir&gt;", this, i18n("Select directory to scan") );
476 if ( fd->exec() == QDialog::Accepted ) {
477 // reset counters;
478 clearCatalogData();
479 subDlevel = 0;
480 QString dirName = fd->selectedFile();
481 // determine bytes to read;
482 kmkSmooth->start();
483 kmkProgress->kPrg->setTotalSteps(0);
484 kmkProgress->kPrg->setProgress(0);
485 kmkProgress->show();
486 QTime t; t.start();
487 break_long_disk_operation = FALSE;
488 bytes_to_read = 0; files_to_read = 0;
489 bytes_to_read_by_traverse( dirName );
490 if ( files_to_read )
492 if( s->dbg() & KMK_DBG_FS_OPS )
493 kdDebug() << "slotFileNew: Time elapsed: " << t.elapsed()
494 << " ms, need to read: " << (bytes_to_read/(1024*1024))
495 << " MB, files to read: " << files_to_read << endl;
496 t.restart();
497 kmkProgress->kPrg->setTotalSteps( files_to_read );
498 // arm timer event; 300 ms interval - can be done in constructor
499 connect( kmkProgressTimer, SIGNAL( timeout() ), this, SLOT( slotUpdateProgressDisp() ) );
500 kmkProgressTimer->start(_KMK_UPDATE_PERIOD,FALSE);
501 // show widget, displaying load progress
502 // start timer
503 traverse_tree( dirName ); // should update total_bytes as it scans
504 update_tree_view();
505 setCatalogStateAndUpdate( Modified );
506 average_file_size = ( total_bytes/total_files );
507 if( s->dbg() & ( KMK_DBG_FS_OPS | KMK_DBG_OTHER ) )
509 kdDebug() << "slotFileNew: Total time: " << t.elapsed() << " ms, read: "
510 << (total_bytes/(1024*1024)) << " MB, files read: " << total_files << endl;
511 kdDebug() << "slotFileNew:STATS: total_files = " << total_files << ";" << endl;
512 kdDebug() << "slotFileNew:STATS: total_folders = " << total_folders << ";" << endl;
513 kdDebug() << "slotFileNew:STATS: total_bytes = " << total_bytes << " B;" << endl;
514 kdDebug() << "slotFileNew:STATS: total_playtime = " << total_play_time << " sec;" << endl;
515 kdDebug() << "slotFileNew:STATS: average_file_size = " << average_file_size << " B;" << endl;
517 kmkProgress->hide();
518 // hide widget
519 // stop timer
520 // Enable actions wich work only with data...
521 if( treeListView->firstChild() )
522 slotTreeListViewCurrentChanged( (QListViewItem*) treeListView->firstChild() );
523 else qWarning( "slotFileNew: NEED SOMEONE TO CREATE THE TREE FOR US...." );
524 QTimer::singleShot( _KMK_UPDATE_PERIOD, this, SLOT( slotFileSaveAs() ) );
526 else
528 setCatalogStateAndUpdate( NoCatalog );
529 clearCatalogData();
530 subDlevel = 0;
531 KMessageBox::sorry( this, i18n("There are no files of supported types in [%1].").arg(dirName),
532 i18n("KDE Music Kataloger") );
535 delete( fd );
538 void kmk::slotFileOpen()
540 if( CatalogState == Modified )
541 switch( KMessageBox::questionYesNoCancel( this,
542 i18n("The catalog [%1] contains unsaved changes.\n"
543 "Do you want to save the changes?")
544 .arg(catalogFileName),
545 i18n("KDE Music Kataloger") ) ) {
546 case KMessageBox::Yes: // Save
547 slotFileSave();
548 break;
549 case KMessageBox::No: // Discard - just continue
550 break;
551 case KMessageBox::Cancel: // Cancel creating new catalog
552 return;
553 break;
555 QFileDialog* fd = new QFileDialog( this, "file dialog", TRUE );
556 fd->setCaption( i18n("Select catalog file to load") );
557 fd->setMode( QFileDialog::ExistingFile );
558 fd->setFilter( i18n("All KMK catalog files %1").arg("(*.kmk)") );
559 if ( fd->exec() != QDialog::Accepted ) delete fd;
560 else
562 QString fileName = fd->selectedFile(); delete fd;
563 qApp->processEvents();
564 loadCatalog( fileName );
565 update_tree_view();
566 Q_CHECK_PTR( s );
567 s->setLastCatalogUsed( fileName );
568 s->saveSettings( kapp->config() );
572 void kmk::slotCatalogAddNewFolder()
574 QFileDialog* fd = new QFileDialog( this, "file dialog", TRUE );
575 fd->setCaption( i18n("Select directory to scan") );
576 fd->setMode( QFileDialog::Directory );
577 fd->setFilter( "*" );
578 /* KFileDialog * t = new KFileDialog( ":&lt;scandir&gt;", "*", this, i18n("Select directory to scan"), TRUE );
579 t->exec(); */
580 // = KFileDialog::getExistingDirectory( ":&lt;scandir&gt;", this, i18n("Select directory to scan") );
581 if ( fd->exec() == QDialog::Accepted )
583 subDlevel = 0;
584 QString dirName = fd->selectedFile();
585 if ( catalog_has_dir( dirName ) )
587 KMessageBox::sorry( this, i18n("Sorry, but the folder [%1]\nis already present in this catalog!")
588 .arg(dirName),i18n("KDE Music Kataloger") );
589 delete fd;
590 return;
592 // clearCatalogData();
593 // reset counters;
594 Q_ULLONG bc01 = total_bytes; Q_ULLONG bc02 = total_files;
595 Q_ULLONG bc03 = total_folders; Q_ULLONG bc04 = total_play_time;
596 bytes_to_read = 0; files_to_read = 0;
597 total_bytes = 0; total_files = 0;
598 total_folders = 0; total_play_time = 0;
599 // determine bytes to read;
600 kmkSmooth->start();
601 kmkProgress->kPrg->setProgress(0);
602 kmkProgress->show();
603 QTime t; t.start();
604 break_long_disk_operation = FALSE;
605 bytes_to_read_by_traverse( dirName );
606 if( s->dbg() & KMK_DBG_FS_OPS )
607 kdDebug() << "slotCatalogAddNewFolder: Time elapsed: " << t.elapsed()
608 << " ms, need to read: " << (bytes_to_read/(1024*1024))
609 << " MB, files to read: " << files_to_read << endl;
610 t.restart();
611 // arm timer event
612 if ( files_to_read )
614 connect( kmkProgressTimer, SIGNAL( timeout() ), this, SLOT( slotUpdateProgressDisp() ) );
615 kmkProgressTimer->start(_KMK_UPDATE_PERIOD,FALSE);
616 // show widget, displaying load progress
617 // start timer
618 traverse_tree( dirName ); // should update total_bytes as it scans
619 update_tree_view();
620 setCatalogStateAndUpdate( Modified );
621 // kdDebug() << "Total time: " << t.elapsed() << " ms, read: " << (total_bytes/(1024*1024))
622 // << " MB, files read: " << total_files << endl;
623 total_bytes += bc01; total_files += bc02;
624 total_folders += bc03; total_play_time += bc04;
625 if ( total_files ) average_file_size = ( total_bytes/total_files );
626 if( s->dbg() & KMK_DBG_FS_OPS & KMK_DBG_OTHER )
628 kdDebug() << "slotCatalogAddNewFolder:STATS: total_files = " << total_files << ";" << endl;
629 kdDebug() << "slotCatalogAddNewFolder:STATS: total_folders = " << total_folders << ";" << endl;
630 kdDebug() << "slotCatalogAddNewFolder:STATS: total_bytes = " << total_bytes << " B;" << endl;
631 kdDebug() << "slotCatalogAddNewFolder:STATS: total_playtime = " << total_play_time << " sec;" << endl;
632 kdDebug() << "slotCatalogAddNewFolder:STATS: average_file_size = " << average_file_size << " B;" << endl;
634 // hide widget
635 kmkProgress->hide();
636 // stop timer
637 // QTimer::singleShot( _KMK_UPDATE_PERIOD, this, SLOT( slotFileSaveAs() ) );
639 else KMessageBox::sorry( this, i18n("There are no files of supported types in [%1].").arg(dirName),
640 i18n("KDE Music Kataloger") );
642 delete( fd );
645 /** Saves the catalog in memory to the file @p catalogFileName;
646 * If the string, being a filename in @p catalogFileName does not have
647 * a .kmk extension - this slot adds it
649 void kmk::slotFileSave()
651 if( CatalogState != Modified ) return;
652 /* We need to check whether catalogFileName exists, and what permissions it has.
653 If there is a problem - inform the user. */
654 bool fileOK=TRUE;
655 if( catalogFileName.isEmpty() || catalogFileName.isNull() )
656 catalogFileName = QDir::homeDirPath()+"/"+_KMK_NEWCATALOG;
657 QFileInfo * _cf = new QFileInfo( catalogFileName );
658 /* Make sure .kmk is appended to the file name...*/
659 if (_cf->extension(FALSE).lower().compare("kmk") != 0)
660 { catalogFileName.append(".kmk"); _cf->setFile( catalogFileName ); }
661 if( (_cf->exists())&&(!_cf->isWritable()) ){ /* File is NOT OK, existing, but not writable. Inform. */
662 fileOK = FALSE;
663 KMessageBox::sorry( this, i18n("Could not open %1 for writing!").arg(catalogFileName),
664 i18n("KDE Music Kataloger") );
666 if( (_cf->exists())&&(!_cf->isFile()) ){ /* File is NOT OK, it is not even a file. Inform. */
667 fileOK = FALSE;
668 KMessageBox::sorry( this, i18n("The filesystem item %1 exists, but it is not a file!").arg(catalogFileName),
669 i18n("KDE Music Kataloger") );
671 if( (FileSaveCalledFromFileSaveAs) && (_cf->exists())
672 && (_cf->isWritable()) ) /* File is OK, but we need to overwrite it... ask what to do. */
674 if( KMessageBox::questionYesNo( this,
675 i18n("A file called [%1] already exists.\nDo you want to overwrite it?").arg(catalogFileName),
676 i18n("KDE Music Kataloger") ) != KMessageBox::Yes ) fileOK = FALSE;
678 FileSaveCalledFromFileSaveAs = FALSE;
679 if( fileOK ) /* After the checks above are passed - lets write! */
681 QDomDocument doc( "KMK_catalog_file" ); QDomElement e = doc.createElement( "KMK_catalog_file_data" );
682 e.setAttribute( "Version", 1 ); doc.appendChild( e );
683 QDomElement e2 = doc.createElement( "CatalogFile_Statistics" ); e.appendChild( e2 );
684 e2.setAttribute( "Total_files_count", (double) total_files );
685 e2.setAttribute( "Total_bytes_count", (double) total_bytes );
686 e2.setAttribute( "Total_dirs_count", (double) total_folders );
687 e2.setAttribute( "Total_secs_count", (double) total_play_time );
688 e2.setAttribute( "Average_file_size", (double) average_file_size );
689 e2 = doc.createElement( "CatalogFile_TreeDescription" ); e.appendChild( e2 );
691 generateListViewXML( treeListView, doc, e2 );
693 QDomElement tag;
694 e2 = doc.createElement( "CatalogFile_Objects" ); e.appendChild( e2 );
695 e2.setAttribute( "Total_MObjs_count", (double) MusicCatalog.count() );
697 for( MmDataList::iterator it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
699 /* ask our DOM Document object to create a new DomElement, and fill this
700 new element with appropriate data */
701 tag = doc.createElement( "MObj" );
702 tag.setAttribute( "Folder", (*it).Folder().utf8() );
703 tag.setAttribute( "Filename", (*it).FileName().utf8() );
704 tag.setAttribute( "Artist", (*it).Artist().utf8() );
705 tag.setAttribute( "Title", (*it).Title().utf8() );
706 tag.setAttribute( "Album", (*it).Album().utf8() );
707 tag.setAttribute( "Genre", (*it).Genre().utf8() );
708 tag.setAttribute( "Comment", (*it).Comment().utf8() );
709 tag.setAttribute( "Year", (*it).Year() );
710 tag.setAttribute( "TrackNumber", (*it).TrackNum() );
711 tag.setAttribute( "Length", (*it).Length() );
712 tag.setAttribute( "Modified", (*it).ModifiedTime() );
713 tag.setAttribute( "Size", (*it).FileSize() );
714 tag.setAttribute( "Channels", (*it).Channels() );
715 tag.setAttribute( "BitRate", (*it).BitRate() );
716 tag.setAttribute( "SampleRate", (*it).SampleRate() );
717 tag.setAttribute( "IsReadOnly", ( ((*it).IsReadOnly()) ? "YES" : "NO" ) );
718 tag.setAttribute( "IsFolder", ( ((*it).IsDir()) ? "YES" : "NO" ) );
719 /* Then, ask the DOM Document to add this new element in the correct place... */
720 e2.appendChild( tag );
723 QString xml = doc.toString(2);
724 QFile file( catalogFileName );
725 if ( file.open( IO_WriteOnly ) ) {
726 file.writeBlock( xml, xml.length() );
727 file.flush();
728 file.close();
729 this->setCaption( i18n("Catalog [%1] - ").arg(catalogFileName) + savedCaption );
730 setCatalogStateAndUpdate( Saved );
731 } else QMessageBox::warning( this, i18n("KDE Music Kataloger"), i18n("Could not open %1 for writing!").arg(catalogFileName),
732 QMessageBox::Cancel,QMessageBox::NoButton,QMessageBox::NoButton );
734 else this->setCaption( i18n("Catalog [%1][UNSAVED] - ").arg(catalogFileName) + savedCaption );
737 void kmk::slotFileSaveAs()
739 QFileDialog* fd = new QFileDialog( 0, "file dialog", TRUE );
740 fd->setCaption( i18n("Select file to save catalog to") );
741 fd->setMode( QFileDialog::AnyFile );
742 fd->setFilter( i18n("All KMK catalog files %1").arg("(*.kmk)") );
743 QString fileName;
744 if ( fd->exec() == QDialog::Accepted )
746 catalogFileName = fd->selectedFile();
747 setCatalogStateAndUpdate( Modified );
748 FileSaveCalledFromFileSaveAs = TRUE;
749 QTimer::singleShot( _KMK_UPDATE_PERIOD, this, SLOT( slotFileSave() ) );
751 delete fd;
754 void kmk::slotCatalogFileClose()
756 if( CatalogState == Modified )
757 switch( KMessageBox::questionYesNoCancel( this,
758 i18n("The catalog [%1] contains unsaved changes.\n"
759 "Do you want to save the changes?")
760 .arg(catalogFileName),
761 i18n("KDE Music Kataloger") ) ) {
762 case KMessageBox::Yes: // Save
763 slotFileSave();
764 break;
765 case KMessageBox::No: // Discard - just continue
766 break;
767 case KMessageBox::Cancel: // Cancel opreation
768 return;
769 break;
771 // Ensure there is no data laying around...
772 clearCatalogData();
775 void kmk::slotCatalogFileStats()
777 QString tmp = formatted_string_from_seconds( total_play_time );
778 KMessageBox::information( 0,
779 i18n("<h3>Catalog statistics</h3>"
780 "<h4>This catalog represents:</h4>"
781 "<pre>Total capacity : %1 MB<br/>"
782 "Total files : %2<br/>"
783 "Total folders : %3<br/>"
784 "Total playtime : %4<br/>"
785 "Average filesize : %5 MB</pre>")
786 .arg(total_bytes/(float)(1024*1024),-8,'f',1).arg(total_files,-8).arg(total_folders,-8)
787 .arg(tmp).arg(average_file_size/(float)(1024*1024),-8,'f',3),
788 i18n("KDE Music Kataloger"));
791 void kmk::slotFileQuit()
793 close();
796 void kmk::closeEvent( QCloseEvent* ce )
798 if( !ce )
800 if( s->dbg() & KMK_DBG_SIGNALS & KMK_DBG_OTHER )
801 kdDebug() << "closeEvent: bad object" << endl;
802 return;
804 if( CatalogState == Modified )
805 switch( KMessageBox::questionYesNoCancel( this,
806 i18n("The catalog [%1] contains unsaved changes.\n"
807 "Do you want to save the changes before exiting?")
808 .arg(catalogFileName),
809 i18n("KDE Music Kataloger") ) ) {
810 case KMessageBox::Yes: // Save & Exit
811 slotFileSave();
812 ce->accept();
813 break;
814 case KMessageBox::No: // Discard - just Exit
815 ce->accept();
816 break;
817 case KMessageBox::Cancel: // Cancel - no nothing
818 ce->ignore();
819 break;
821 else ce->accept();
824 void kmk::slotProgramSettings()
826 // WARNING: kmkSettingsDialog has Qt::WDestructiveClose flag set, it WILL destroy itself
827 // also, it internally calls exec() - so we only create it, passing relevant data
828 // to its contructor
829 uint * k = new uint;
830 s->saveSettings( kapp->config() );
831 new kmkSettingsDialog( k );
832 // if( k ) // config changed? if so - re-read it..
833 s->readSettings( kapp->config() );
834 player->handleConfigChange();
835 /* If we need to choose the font kmk uses to display the lists and stuff,
836 here is how its done:
838 QFont f ( "Courier", 16, 50 );
839 main_container_widget->setFont( f );
843 void kmk::slotTagEdit()
845 MmDataPList *files = new MmDataPList; if( !files ) return;
846 MmData *tmp = 0; // zero-init our MmData pointer
847 KListViewItem *node = 0;
848 if( _popup_at_search )
849 node = (KListViewItem*) searchFilesListView->firstChild();
850 else
851 node = (KListViewItem*) filesListView->firstChild();
853 while ( node )
855 if ( node->isSelected() )
857 tmp = findMmData( node->text( 0 ), node->text( 1 ) );
858 if ( tmp ) if ( (*tmp).IsDir()==FALSE ) files->append( tmp );
860 node = (KListViewItem*) node->nextSibling();
863 if ( files->isEmpty() ) { delete files; return; }
865 /** kmkTagEdit's constructor takes care of everything for us -
866 * we don't even need to call show() or exec() - it is done automagically;
868 uint k = 0;
869 new kmkTagEdit( files, &k );
870 if( s->dbg() & KMK_DBG_SIGNALS & KMK_DBG_OTHER )
871 kdDebug() << "slotTagEdit: TagEditDialog returns: " << k << endl;
872 if( k ) {
873 setCatalogStateAndUpdate( Modified );
874 slotTreeListViewCurrentChanged( treeListView->currentItem() );
876 delete files;
879 void kmk::slotListTableToggleTitle()
881 // QTable* tbl = ((kmkWidget*) kmk_widg )->FileList();
882 // tbl->hideColumn(1);
885 void kmk::slotPlayerPrevious()
887 player->previous();
890 void kmk::slotPlayerPlay()
892 player->play();
895 void kmk::slotPlayerPause()
897 player->pause();
900 void kmk::slotPlayerStop()
902 player->stop();
905 void kmk::slotPlayerNext()
907 player->next();
911 * In this member we use the fact, that when QTable's selectionMode() is MultiRow,
912 * meaning that whole rows selection is only allowed, QTable passes these
913 * rows as seperate selections - 2 successive rows will give us 2 selections
914 * with each selections topRow() and bottomRow() showing the number of the selected
915 * row in the table ---THIS ALL IS FALSE
917 void kmk::slotPlayerPlaySelection()
919 MmDataPList *files = new MmDataPList;
920 MmData *tmp = 0; // zero-init our MmData pointer
921 KListViewItem *node = 0;
922 if( _popup_at_search)
923 node = (KListViewItem*) searchFilesListView->firstChild();
924 else
925 node = (KListViewItem*) filesListView->firstChild();
927 while ( node )
929 if ( node->isSelected() )
931 tmp = findMmData( node->text( 0 ), node->text( 1 ) );
932 if ( tmp ) if ( (*tmp).IsDir()==FALSE ) files->append( tmp );
934 node = (KListViewItem*) node->nextSibling();
937 QString flNm = "/tmp/kmk/kmk_lst_"+QString::number( random() )+".m3u";
938 QFile file( flNm );
939 if ( file.open( IO_WriteOnly ) )
941 QTextStream stream( &file );
942 stream << "#EXTM3U" << endl;
943 for ( tmp = files->first(); tmp; tmp = files->next() )
945 stream << "#EXTINF:" << tmp->Length() << ", " << tmp->Artist() << " - " << tmp->Title() << endl;
946 stream << tmp->Folder() << "/" << tmp->FileName() << endl;
948 file.close();
950 player->playPlaylist( flNm );
952 // file.remove();
954 else QMessageBox::warning( this, i18n("KDE Music Kataloger"),i18n("Could not open %1 for writing!").arg(flNm),
955 QMessageBox::Cancel,QMessageBox::NoButton,QMessageBox::NoButton );
956 delete files;
959 void kmk::slotPlayerEnqueue()
961 MmDataPList *files = new MmDataPList;
962 MmData *tmp = 0; // zero-init our MmData pointer
963 KListViewItem *node = 0;
964 if( _popup_at_search)
965 node = (KListViewItem*) searchFilesListView->firstChild();
966 else
967 node = (KListViewItem*) filesListView->firstChild();
969 while ( node )
971 if ( node->isSelected() )
973 tmp = findMmData( node->text( 0 ), node->text( 1 ) );
974 if ( tmp ) if ( (*tmp).IsDir()==FALSE ) files->append( tmp );
976 node = (KListViewItem*) node->nextSibling();
979 QString flNm = "/tmp/kmk/kmk_lst_"+QString::number( random() )+".m3u";
980 QFile file( flNm );
981 if ( file.open( IO_WriteOnly ) )
983 QTextStream stream( &file );
984 stream << "#EXTM3U" << endl;
985 for ( tmp = files->first(); tmp; tmp = files->next() )
987 stream << "#EXTINF:" << tmp->Length() << ", " << tmp->Artist() << " - " << tmp->Title() << endl;
988 stream << tmp->Folder() << "/" << tmp->FileName() << endl;
990 file.close();
992 player->addPlaylist( flNm );
995 else QMessageBox::warning( this, i18n("KDE Music Kataloger"),i18n("Could not open %1 for writing!").arg(flNm),
996 QMessageBox::Cancel,QMessageBox::NoButton,QMessageBox::NoButton );
997 delete files;
1001 void kmk::slotPlayerEnqueueDir()
1003 _kmk_include_subdirs = FALSE; //tell playerDir to NOT include subfolder(s) contents
1004 playerDir( A__ENQUEUE ); // here we enqueue
1007 void kmk::slotPlayerEnqueueDirSubdirs()
1009 _kmk_include_subdirs = TRUE; //tell playerDir to include subfolder(s) contents
1010 playerDir( A__ENQUEUE ); // here we enqueue
1013 void kmk::slotPlayerPlayDir()
1015 _kmk_include_subdirs = FALSE; //tell playerDir to NOT include subfolder(s) contents
1016 playerDir( A__PLAY ); // and here we play
1019 void kmk::slotPlayerPlayDirSubdirs()
1021 _kmk_include_subdirs = TRUE; //tell playerDir to include subfolder(s) contents
1022 playerDir( A__PLAY ); // and here we play
1025 void kmk::slotCatalogUpdateSubtree()
1027 // init *lv* to currently selected item in the Tree view
1028 QListViewItem* lv = treeListView->currentItem();
1029 // this one will hold the full-blown path to the folder we should update
1030 QString folder_name;
1031 // this loop extracts a full path of the selected dir in the Tree view by
1032 // going from it to the top, collecting data on the way in *folder_name*
1033 while ( TRUE ) {
1034 folder_name.prepend( lv->text(0) );
1035 if( lv->parent() )
1036 { if( lv->parent()->text(0).compare("/") != 0 ) folder_name.prepend( "/" ); lv = lv->parent(); }
1037 // when the top is reached - the loop "break"s
1038 else break;
1040 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1042 kdDebug() << "slotCatalogUpdateSubtree: should update " << folder_name << endl;
1043 kdDebug() << "slotCatalogUpdateSubtree: subDlevel shows " << subDlevel << "; zeroing it..." << endl;
1045 subDlevel=0;
1046 traverseUpdate_tree( folder_name );
1047 if( tree_needs_update )
1049 tree_needs_update = FALSE;
1050 update_tree_view();
1051 if( MusicCatalog.isEmpty() ) setCatalogStateAndUpdate( NoCatalog );
1052 else setCatalogStateAndUpdate( Modified );
1056 void kmk::slotTreeListViewPopupMenuRequested( QListViewItem* itm, const QPoint &pos, int col )
1058 /* Take care of annoying warnings ..... */
1059 Q_UNUSED(itm); Q_UNUSED(col);
1060 if( treeListView->currentItem()>0 )
1061 catalogUpdateSubtreeAction->setEnabled( TRUE );
1062 else
1063 catalogUpdateSubtreeAction->setEnabled( FALSE );
1064 CTree_PopupMenu->popup( pos );
1067 void kmk::slotTreeListViewCurrentChanged( QListViewItem * itm )
1069 if ( MusicCatalog.isEmpty() ) { kdDebug() << "LVupdate: called with empty catalog" << endl; return; }
1070 // TagEditAction->setEnabled( FALSE );
1071 QListViewItem* lv = itm; QString p;
1072 // kdDebug() << "treeListView dropHighlighter() says: " << treeListView->dropHighlighter() << endl;
1074 // QTime ptime; ptime.start();
1076 while ( TRUE ) {
1077 p.prepend( lv->text(0) );
1078 if( lv->parent() ) { if( lv->parent()->text(0).compare("/") != 0 ) p.prepend( "/" ); lv = lv->parent(); }
1079 else break;
1081 // kdDebug() << "LVupdate: user clicked: " << p << endl;
1082 // kdDebug() << "(kmk):LVupdate: determine hole selected folder name time: " << ptime.elapsed() << " ms." << endl;
1083 // ptime.restart();
1085 // Find the MmData folder item and get the number of files it contains
1086 MmDataList::iterator it;
1087 bool found = FALSE;
1088 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1089 if( ((*it).IsDir()) && ( (*it).Folder().compare(p)==0 ) ){ found = TRUE; break; }
1090 int folder_files = (found) ? (*it).FileSize() : 0;
1092 // kdDebug() << "(kmk):LVupdate: locate folder as *MmData time: " << ptime.elapsed() << " ms. ff=" << folder_files << endl;
1093 // ptime.restart();
1095 // disble list for manipulation : flicker care
1096 filesListView->setEnabled( FALSE );
1097 while( folder_files > filesListView->childCount() )
1098 new KListViewItem( (QListView*) filesListView, 0 );
1099 while( folder_files < filesListView->childCount() ) {
1100 KListViewItem* t = (KListViewItem*) filesListView->lastItem();
1101 if ( t ) delete t;
1103 // kdDebug() << "(kmk):LVupdate: table setup time: " << ptime.elapsed() << " ms." << endl;
1104 // ptime.restart();
1106 // If there are items, go to the next...
1107 if ( found ) {
1108 it++;
1109 // ...and find the first MmData item, which is inside folder *p*
1110 // The result should be files, because MmData folder's Folder() holds their full path,
1111 // and there should be only one folder with *p* as Folder(), and we are already past that...
1112 while ( (( (*it).Folder().compare(p)!=0 )) ) it++;
1114 QDateTime dt;
1115 KListViewItem * sel_item = 0;
1116 KListViewItem * new_item = (KListViewItem*) filesListView->firstChild();
1117 // This loop's main requirement is that files within a dir are consecutive
1118 for ( int k=0; k<folder_files; it++,k++ )
1120 if ( !new_item ) qFatal("Something horrible happened!");
1121 if ( locateMode )
1122 if ( ( (*it).Folder().compare( DelSelDir ) == 0 ) && ( (*it).FileName().compare( DelSelFile ) == 0 ) )
1123 { sel_item = new_item; locateMode = FALSE; }
1124 new_item->setText( 0, (*it).Folder() ); // folder - for use in play or enqueue file(s) action
1125 new_item->setText( 1, (*it).FileName() ); // file
1126 new_item->setText( 2, (*it).Artist() ); // artist
1127 new_item->setText( 3, (*it).Title() ); // title
1128 new_item->setText( 4, (*it).Album() ); // album
1129 new_item->setText( 5, (*it).Genre() ); // genre
1130 new_item->setText( 6, (*it).Comment() ); // comment
1131 new_item->setText( 7, ((*it).Year()!=0)?QString::number( (*it).Year() ):"" ); // song year
1132 new_item->setText( 8, ((*it).TrackNum()!=0)?QString::number( (*it).TrackNum() ):"" ); // track number
1133 new_item->setText( 9, formatted_string_from_seconds( (*it).Length() ) ); // length
1134 new_item->setText( 10, QString::number( (*it).FileSize() / 1024 ) + " kB" ); // file size
1135 // dt.setTime_t( (*it).ModifiedTime() );
1136 new_item->setText( 11, QString::number( (*it).BitRate()) ); // bitrate
1137 new_item->setText( 12, QString::number( (*it).SampleRate()) ); // samplerate
1138 new_item->setText( 13, QString::number( (*it).Channels()) ); // channels
1139 QFileInfo ext_info( (*it).FileName() );
1140 new_item->setText( 14, ext_info.extension(FALSE).upper() ); // type
1141 new_item->setText( 15, (*it).IsReadOnly()?"YES":"_no_" ); // read only
1142 new_item = (KListViewItem*) new_item->nextSibling();
1145 // kdDebug() << "(kmk):LVupdate: table fill time: " << ptime.elapsed() << " ms." << endl;
1146 // ptime.restart();
1148 // update table contets after manipulation
1149 filesListView->clearSelection();
1150 // here we update the first 5 columns' width
1151 Q_CHECK_PTR( s );
1152 // honor user setting whether he/she wants auto-column-width, or likes speed more
1153 if ( s->autoColumnWidth() )
1154 for ( ushort k=0; k<7; k++) filesListView->adjustColumn( k );
1155 if ( sel_item ) {
1156 filesListView->ensureItemVisible( sel_item );
1157 filesListView->setSelected( sel_item, TRUE );
1158 DelSelDir = ""; DelSelFile = "";
1160 filesListView->setEnabled( TRUE );
1163 void kmk::slotFileListTablePopupMenuRequested( QListViewItem* itm, const QPoint &pos, int col )
1165 Q_UNUSED(itm); Q_UNUSED(col);
1166 _popup_at_search = FALSE;
1167 CList_PopupMenu->exec( pos );
1170 void kmk::slotSearchListTablePopupMenuRequested( QListViewItem* itm, const QPoint &pos, int col )
1172 Q_UNUSED(itm); Q_UNUSED(col);
1173 _popup_at_search = TRUE;
1174 CSearchList_PopupMenu->exec( pos );
1178 * This function locates the correct place in the catalog tree, selects the
1179 * correct folder and then informs the method updating
1180 * the file list to select AND show the reqested file
1182 void kmk::slotLocateRequested()
1184 // this below is for locating results in the same folder to work,
1185 // without the need to manually change the currently selected folder
1186 // !!! kmk_widg->TreeList()->setCurrentItem( listViewRootItem );
1187 KListViewItem *node = (KListViewItem*) searchFilesListView->firstChild();
1188 if ( !node ) return;
1190 // inform the apropriate method that we are in locate mode:
1191 // set all relevant vars
1192 while ( node )
1194 // as we are called - and this is LOCATE slot - only ONE item in the search
1195 // results should be selected; if there are more - they are ignored
1196 if ( node->isSelected() )
1198 DelSelDir = node->text( 0 );
1199 DelSelFile = node->text( 1 );
1200 locateMode = TRUE;
1201 break;
1203 node = (KListViewItem*) node->nextSibling();
1206 // kdDebug() << " locating " << DelSelDir << " | " << DelSelFile << endl;
1207 QString ci;
1208 // get a list of all listview items to iterate over them
1209 QListViewItemIterator it( treeListView );
1210 while ( it.current() )
1212 QListViewItem *item = it.current();
1213 ci = QString::null;
1214 // this while loop exits when it reaches top level; result is slash ("/") separated path in ci
1215 bool not_found = TRUE;
1216 while ( not_found ) {
1217 ci.prepend( item->text(0) );
1218 if( item->parent() ) { if( item->parent()->text(0).compare("/") != 0 ) ci.prepend( "/" ); item = item->parent(); }
1219 else break;
1221 // kdDebug() << " ci now is " << ci << endl;
1222 item = it.current();
1223 if ( DelSelDir.compare( ci ) == 0 )
1225 treeListView->ensureItemVisible ( item );
1226 if( treeListView->currentItem() == item )
1227 slotTreeListViewCurrentChanged( item );
1228 else
1229 treeListView->setCurrentItem( item );
1230 not_found = FALSE;
1231 break;
1233 ++it;
1235 // switch from search tab to catalog view tab
1236 tabWidget1->setCurrentPage(0);
1239 void kmk::slotSearchButtonClicked()
1241 // if( MusicCatalog.isEmpty() ) return;
1242 QString p = leSearchFor->text();
1243 // TagEditAction->setEnabled( FALSE );
1245 // QTime ptime; ptime.start();
1247 QDateTime dt; long new_rows=0;
1248 // disble table for manipulation : flicker care
1249 searchFilesListView->setEnabled( FALSE );
1250 // Count the actuall matches
1251 for( MmDataList::iterator it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1253 if( (!(*it).IsDir()) && ( ((*it).Folder().compare(p)==0) || (((*it).FileName().contains(p,FALSE)>0) ||
1254 ((*it).Artist().contains(p,FALSE)>0) ) || (((*it).Title().contains(p,FALSE)>0) ||
1255 ((*it).Album().contains(p,FALSE)>0) ) ) )
1256 { new_rows++; }
1258 // kdDebug() << new_rows << " rows should be added." << endl;
1259 // If the current search KListView has too many items (rows) - remove the unneeded
1260 while( new_rows > searchFilesListView->childCount() )
1261 new KListViewItem( (QListView*) searchFilesListView, 0 );
1262 // If the search KListView's items count is low - add the needed items (rows)
1263 while( new_rows < searchFilesListView->childCount() ) {
1264 KListViewItem* t = (KListViewItem*) searchFilesListView->lastItem();
1265 if ( t ) delete t;
1267 // kdDebug() << "(kmk):LVupdate: table setup time: " << ptime.elapsed() << " ms." << endl;
1268 // ptime.restart();
1270 // If there are any matches at all...
1271 if ( new_rows )
1273 // use this var as a counter
1274 new_rows = 0;
1275 // and new_item is a pointer we'll set to the item we are currently adjusting...
1276 // all the items we need should have been added with the previous loops
1277 KListViewItem * new_item = (KListViewItem*) searchFilesListView->firstChild();
1278 // This is the exact same loop as above, but here will do something with what matches
1279 for( MmDataList::iterator it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1281 if( (!(*it).IsDir()) && ( ((*it).Folder().compare(p)==0) || (((*it).FileName().contains(p,FALSE)>0) ||
1282 ((*it).Artist().contains(p,FALSE)>0) ) || (((*it).Title().contains(p,FALSE)>0) ||
1283 ((*it).Album().contains(p,FALSE)>0) ) ) )
1285 if ( !new_item ) qFatal("slotSearchButtonClicked: supposedly \"equal\" checks are giving "
1286 "different results!");
1287 new_item->setText( 0, (*it).Folder() ); // folder - for use in play or enqueue file(s) action
1288 new_item->setText( 1, (*it).FileName() ); // file
1289 new_item->setText( 2, (*it).Artist() ); // artist
1290 new_item->setText( 3, (*it).Title() ); // title
1291 new_item->setText( 4, (*it).Album() ); // album
1292 new_item->setText( 5, (*it).Genre() ); // genre
1293 new_item->setText( 6, (*it).Comment() ); // comment
1294 new_item->setText( 7, ((*it).Year()!=0)?QString::number( (*it).Year() ):"" ); // song year
1295 new_item->setText( 8, ((*it).TrackNum()!=0)?QString::number( (*it).TrackNum() ):"" ); // track number
1296 new_item->setText( 9, formatted_string_from_seconds( (*it).Length() ) ); // length
1297 new_item->setText( 10, QString::number( (*it).FileSize() / 1024 ) + " kB" ); // file size
1298 // dt.setTime_t( (*it).ModifiedTime() );
1299 new_item->setText( 11, QString::number( (*it).BitRate()) ); // bitrate
1300 new_item->setText( 12, QString::number( (*it).SampleRate()) ); // samplerate
1301 new_item->setText( 13, QString::number( (*it).Channels()) ); // channels
1302 QFileInfo ext_info( (*it).FileName() );
1303 new_item->setText( 14, ext_info.extension(FALSE).upper() ); // type
1304 new_item->setText( 15, (*it).IsReadOnly()?"YES":"_no_" ); // read only
1305 new_item = (KListViewItem*) new_item->nextSibling();
1306 new_rows++;
1309 // kdDebug() << new_rows << " rows have been added." << endl;
1310 // update table contets after manipulation
1311 searchFilesListView->clearSelection();
1312 // here we update the first 5 columns' width
1313 Q_CHECK_PTR( s );
1314 // honor user setting whether he/she wants auto-column-width, or likes speed more
1315 if ( s->autoColumnWidth() )
1316 for ( ushort k=0; k<7; k++) searchFilesListView->adjustColumn( k );
1318 searchFilesListView->setEnabled( TRUE );
1319 if(!(new_rows)) KMessageBox::sorry( this, i18n("No files were found, that match your search criterias!"),
1320 i18n("KDE Music Kataloger") );
1323 void kmk::slotClearButtonClicked()
1325 leSearchFor->clear();
1326 searchFilesListView->clear();
1329 void kmk::slotDoneButtonClicked()
1331 filesListView->clearSelection();
1332 tabWidget1->setCurrentPage(0);
1337 /**============================================================================================================**
1338 ** NON SLOTS NON SLOTS NON SLOTS NON SLOTS NON SLOTS NON SLOTS NON SLOTS **
1339 **============================================================================================================**/
1341 void kmk::clearCatalogData( const bool UpdateState )
1343 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1344 kdDebug() << "clearCatalogData: clearing all in-memory catalog data and stats...." << endl;
1345 // clear catalog tree
1346 MusicCatalog.clear();
1347 treeListView->clear();
1348 listViewRootItem = (KListViewItem*) treeListView;
1349 cur_vlItem = listViewRootItem;
1350 // clear table showing files
1351 filesListView->clear();
1352 // clear table showing search result files
1353 searchFilesListView->clear();
1354 // reset stats counters, locate mode helper variables
1355 locateMode = FALSE; DelSelFile = ""; DelSelDir = "";
1356 bytes_to_read = 0; total_bytes = 0; total_files = 0;
1357 total_folders = 0; total_play_time = 0; average_file_size = 0;
1358 // and finally, update state, title and actions accordingly
1359 if( UpdateState ) setCatalogStateAndUpdate( NoCatalog );
1360 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1361 kdDebug() << "clearCatalogData: ...done!" << endl;
1364 void kmk::setCatalogStateAndUpdate( const kmk::CatalogStateEnum state )
1366 if( s->dbg() & KMK_DBG_OTHER )
1367 switch (state) {
1368 case kmk::NoCatalog:
1369 kdDebug() << "setCatalogStateAndUpdate: setting catalog state to \"NO CATALOG\"..." << endl;
1370 break;
1371 case kmk::Modified:
1372 kdDebug() << "setCatalogStateAndUpdate: setting catalog state to \"MODIFIED\"..." << endl;
1373 break;
1374 case kmk::Saved:
1375 kdDebug() << "setCatalogStateAndUpdate: setting catalog state to \"SAVED\"..." << endl;
1376 break;
1377 default:
1378 kdDebug() << "setCatalogStateAndUpdate: setting catalog state to \"UNSPECIFIED\"..." << endl;
1379 break;
1381 CatalogState = state;
1382 switch (state) {
1383 case kmk::NoCatalog:
1384 catalogFileName = QDir::homeDirPath()+"/"+_KMK_NEWCATALOG;
1385 this->setCaption( i18n("No catalog - ") + savedCaption );
1386 fileSaveAction->setEnabled( FALSE );
1387 fileSaveAsAction->setEnabled( FALSE );
1388 fileCatalogFileStats->setEnabled( FALSE );
1389 fileCatalogFileClose->setEnabled( FALSE );
1390 catalogAddNewFolderAction->setEnabled( FALSE );
1391 TagEditAction->setEnabled( TRUE );
1392 listTableLocateAction->setEnabled( FALSE );
1393 playerEnqueueAction->setEnabled( FALSE );
1394 playerEnqueueDirAction->setEnabled( FALSE );
1395 playerEnqueueDirSubdirsAction->setEnabled( FALSE );
1396 playerPlayDirAction->setEnabled( FALSE );
1397 playerPlayDirSubdirsAction->setEnabled( FALSE );
1398 playerPlaySelectionAction->setEnabled( FALSE );
1399 main_container_widget->setEnabled( FALSE );
1400 break;
1401 case kmk::Modified:
1402 this->setCaption( i18n("Catalog [%1][UNSAVED] - ").arg(catalogFileName) + savedCaption );
1403 fileSaveAction->setEnabled( TRUE );
1404 fileSaveAsAction->setEnabled( TRUE );
1405 fileCatalogFileStats->setEnabled( TRUE );
1406 fileCatalogFileClose->setEnabled( TRUE );
1407 catalogAddNewFolderAction->setEnabled( TRUE );
1408 TagEditAction->setEnabled( TRUE );
1409 listTableLocateAction->setEnabled( TRUE );
1410 playerEnqueueAction->setEnabled( TRUE );
1411 playerEnqueueDirAction->setEnabled( TRUE );
1412 playerEnqueueDirSubdirsAction->setEnabled( TRUE );
1413 playerPlayDirAction->setEnabled( TRUE );
1414 playerPlayDirSubdirsAction->setEnabled( TRUE );
1415 playerPlaySelectionAction->setEnabled( TRUE );
1416 main_container_widget->setEnabled( TRUE );
1417 break;
1418 case kmk::Saved:
1419 this->setCaption( i18n("Catalog [%1] - ").arg(catalogFileName) + savedCaption );
1420 fileSaveAction->setEnabled( FALSE );
1421 fileSaveAsAction->setEnabled( TRUE );
1422 fileCatalogFileStats->setEnabled( TRUE );
1423 fileCatalogFileClose->setEnabled( TRUE );
1424 catalogAddNewFolderAction->setEnabled( TRUE );
1425 TagEditAction->setEnabled( TRUE );
1426 listTableLocateAction->setEnabled( TRUE );
1427 playerEnqueueAction->setEnabled( TRUE );
1428 playerEnqueueDirAction->setEnabled( TRUE );
1429 playerEnqueueDirSubdirsAction->setEnabled( TRUE );
1430 playerPlayDirAction->setEnabled( TRUE );
1431 playerPlayDirSubdirsAction->setEnabled( TRUE );
1432 playerPlaySelectionAction->setEnabled( TRUE );
1433 main_container_widget->setEnabled( TRUE );
1434 break;
1435 default:
1436 break;
1438 if( s->dbg() & KMK_DBG_OTHER )
1439 kdDebug() << "setCatalogStateAndUpdate: done!" << endl;
1442 /* WARNING !!! OBSESIVE USE OF RECURSION!!!!
1444 unsigned long kmk::traverse_tree( const QString& dir )
1446 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
1447 { kmkSmooth->restart(); qApp->processEvents(); }
1448 /* Here we mark our tree depth - subDlevel is a kmk private var */
1449 subDlevel++;
1450 //qWarning( "looking in \""+dir+"\"...");
1451 /* Create a QDir object, set to the "dir-for-scan"
1452 The filter is set to DIRECTORIEs, without symlinks */
1453 QDir d ( dir );
1454 d.setFilter( QDir::Dirs | QDir::NoSymLinks );
1455 /* This gets our "dir-for-scan" subdirs' in @p list */
1456 const QFileInfoList *list = d.entryInfoList();
1457 QFileInfoListIterator it( *list );
1458 /* @p fi is used to get the dir name and last modification time */
1459 QFileInfo *fi = new QFileInfo( d.absPath() );
1460 QString parent_dir = d.absPath();
1461 if( subDlevel == 1 )
1462 parent_dir = "TREE_BASE";
1463 else
1464 parent_dir.setLength( parent_dir.length() - (d.dirName().length() + 1) );
1465 if( s->dbg() & KMK_DBG_FS_OPS )
1466 kdDebug() << "traverse_tree: parent_dir set to : " << parent_dir << endl;
1467 /* Create a new MmDataList entry with the extracted above data
1468 for the "dir-for-scan" and set @p new_dir to point at it;
1469 we need a pointer to this new item, because if we add any files,
1470 contained in this dir, or its subdirs, we must update its
1471 MmFileSize field according to this below... */
1472 MmDataList::iterator new_dir =
1473 /* Some explanation : as we use the same elements to store info
1474 for audio files, and the dirs containing them - we do this trick:
1475 if the MmData item holds info on DIRECTORY - we set its fields like this:
1476 MmFolder - set to the ABSOLUTE DIR NAME
1477 MmFileName - set to the SHORT DIR NAME
1478 MmIsFolder - set to TRUE
1479 MmReadOnly - wether or not we have permission to write in DIR
1480 MmLength - set to the number of files in "dir-to-scan" subdirs
1481 MmFileSize - set to the number of files contained - updated later */
1482 MusicCatalog.append( MmData( d.absPath(), // folder
1483 d.dirName(), // filename
1484 "", // artist
1485 "", // title
1486 "", // album
1487 "", // genre
1488 parent_dir, // comment
1489 0, // year
1490 0, // track number
1491 0, // file size - updated later
1492 fi->lastModified().toTime_t(), // modified time
1493 fi->isWritable(), // read only? - we may use this to determine if we can "rename" dirs
1494 TRUE, // is dir?
1495 0, // audio duration (length) in secs - upd. later
1496 0, // channels
1497 0, // samplerate
1498 0 ) ); // bitrate
1499 /* We are done with @p fi for now - so, get rid of it */
1500 delete fi;
1501 /* Create a new KListView entry, pointed by @p ptr and if it is at
1502 the catalog root make its name the absolute path
1503 to "dir-for-scan", otherwise use short dir name;
1504 we set this item's parent to be what's pointed by kmk's private
1505 var cur_vlItem - it should point to what should be this item's
1506 parent in the catalog tree */
1507 // KListViewItem *ptr;
1508 //////////////////////// kdDebug() << "about to crash..." << endl;
1509 // if ( subDlevel == 1 ) ptr = new KListViewItem( (QListView*) treeListView, d.absPath() );
1510 // else ptr = new KListViewItem( (QListViewItem*) cur_vlItem, d.dirName() );
1511 //////////////////////// kdDebug() << "Strange, did not crash...." << endl;
1512 /* Set the new catalog tree item to be closed, non-expandable:
1513 if it happens to be parent of another - we will fix it later */
1514 // ptr->setOpen( FALSE ); ptr->setExpandable( FALSE );
1515 /* Here we initialize our subdirs' loaded files counter @p lf */
1516 unsigned long lf = 0;
1517 /* This checks if "dir-for-scan" has any subdirs */
1518 if ( !list->isEmpty() )
1520 /* And if it has, we go over the list of "dir-for-scan" subdirs */
1521 while ( (fi = it.current()) != 0 )
1523 /* Check if these subdirs are not the FS reserved "." and ".."
1524 FIX: AND that we can also access them !!! */
1525 if ( (fi->isReadable()) && (fi->isExecutable()) )
1526 if ( (fi->fileName().compare(".")!=0) && (fi->fileName().compare("..")!=0) )
1528 /* So, we've found a valid "dir-for-scan" subdirectory.
1529 If you remember - we used kmk's private var cur_vlItem as a parent
1530 of the KListViewItem we added earlier; so we want it to be
1531 "preset" when we are called, don't we? Let's make sure it is! */
1532 // cur_vlItem = ptr;
1533 /* We are setup and ready to call ourselves recursievely on this one.
1534 Because at the end of traverse_tree we provide a count of the
1535 actually added files to the catalog, including the added files
1536 from each subdir, lets increase @p lf with the numer of files in
1537 the directory we want to check...
1538 Here is the tracking : */
1539 lf += traverse_tree( d.filePath( fi->fileName() ) );
1541 /* Then go to the next in the list */
1542 ++it;
1544 /* We finished calling ourselves recursievly. So, if the subdirs'
1545 scan added any files - set the new catalog item to represent this
1546 accordingly - we hold subentries, so we need to be expandable;
1547 Maybe the user could set wheter or not the new catalog is already
1548 open or collapsed */
1549 // if ( lf > 0 ) { ptr->setOpen( FALSE ); ptr->setExpandable( TRUE ); }
1550 //qWarning("having "+QString::number(lf)+" files reported back...");
1552 /* Then we set our "dir-for-scan" filter to files, without symlinks */
1553 d.setFilter( QDir::Files | QDir::NoSymLinks );
1554 /* And get a list of its contents, after the filtering is apllied */
1555 const QFileInfoList *list2 = d.entryInfoList();
1556 QFileInfoListIterator it2( *list2 );
1557 QFileInfo *fi2;
1558 /* These we will use for storage of the exctracted data from files */
1559 QString art, ttl, alb, gen, cmt;
1560 /* This will be our "dir-for-scan" ONLY (i.e. without including
1561 the count from the subdirs) loaded files counter; zero-init it.
1562 Nowadays there are file systems, capable of handling enormous amounts
1563 of files in a single directory - make sure we don't choke on someones
1564 huge collection, by using extra large counter.*/
1565 unsigned long loaded_files = 0;
1566 /* A cycle to iterate over the files in "dir-for-scan" */
1567 while ( (fi2 = it2.current()) != 0 )
1569 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
1570 { kmkSmooth->restart(); qApp->processEvents(); }
1571 /* If the files aren't with the supported extensions - ignore them;
1572 We call QFileInfo->extension(FALSE) because we need only chars after LAST '.' */
1573 if ( fi2->isReadable() )
1574 // LOSSLESS first
1575 if ( ( fi2->extension(FALSE).lower().compare("wv" ) == 0 ) || // wav-pack
1576 ( fi2->extension(FALSE).lower().compare("tta" ) == 0 ) || // true-audio
1577 ( fi2->extension(FALSE).lower().compare("ape" ) == 0 ) || // monkey's audio
1578 /* Is it correct for a FLAC file to have any other extension than "flac"?
1579 For example - can it be named "my_audio_file.fla"? Ideas, advices.... */
1580 ( fi2->extension(FALSE).lower().compare("flac") == 0 ) || // flac
1581 // LOSSY next
1582 ( fi2->extension(FALSE).lower().compare("mp3") == 0 ) || // mp3
1583 ( fi2->extension(FALSE).lower().compare("ogg") == 0 ) || // ogg-vorbis
1584 ( fi2->extension(FALSE).lower().compare("spx") == 0 ) || // ogg-speex
1585 ( fi2->extension(FALSE).lower().compare("mpc") == 0 ) ) // muse-pack
1587 /* Init our storage for this file... */
1588 art = ""; ttl = ""; alb = ""; gen = ""; cmt = "";
1589 using namespace TagLib;
1590 /* Use TagLib's functions to extract meta data and audio properties;
1591 This tells TagLib to read tags, and be as fast as possible */
1592 if( s->dbg() & KMK_DBG_FS_OPS )
1593 kdDebug() << "traverse_tree: Now checking: " << d.filePath( fi2->fileName().latin1() ) << endl;
1594 TagLib::FileRef f( d.filePath( fi2->fileName().latin1() ), TRUE, TagLib::AudioProperties::Fast );
1595 /* If TagLib found the file to be valid - then add it to the catalog.
1596 Maybe here would be a good place to update some global catalog stats,
1597 like total storage used by the collection we are cataloguining, number
1598 of files in it, average bitrate, highest one, lowest one, biggest file,
1599 smallest file... and anything else someone might consider "interesting"!*/
1600 if( f.file()->isValid() )
1602 TagLib::String s;
1603 s = f.tag()->artist(); art = s.toCString();
1604 s = f.tag()->title(); ttl = s.toCString();
1605 s = f.tag()->album(); alb = s.toCString();
1606 s = f.tag()->genre(); gen = s.toCString();
1607 s = f.tag()->comment(); cmt = s.toCString();
1608 /* Some of the extracted file data is in our storage vars now.
1609 Create a new MmData item, and fill it with what we've read. */
1610 MmData ni = MmData();
1611 ni.setFolder( d.absPath() );
1612 ni.setFileName( fi2->fileName() );
1613 ni.setArtist( art );
1614 ni.setTitle( ttl );
1615 ni.setAlbum( alb );
1616 ni.setGenre( gen );
1617 ni.setComment( cmt );
1618 ni.setYear( f.tag()->year() );
1619 ni.setTrackNum( f.tag()->track() );
1620 ni.setFileSize( fi2->size() );
1621 // qDebug( "To "+QString::number( total_bytes )+" B, adding "+QString::number((Q_ULLONG) fi2->size())+" B." );
1622 total_bytes += (Q_ULLONG) fi2->size();
1623 ni.setModifiedTime( fi2->lastModified().toTime_t() );
1624 /* How about using Qt's functions QFile and QDir to check this?
1625 Not a problem! I've checked it - TagLib gives correct data; in a bunch of mp3s, I
1626 set to 2 of them to have only read acces - TagLib said exactly this! The only thing
1627 left in mind is speed, but... how do I check IT? Pl.Petrov */
1628 ni.setIsReadOnly( f.file()->readOnly() );
1629 ni.setIsDir( FALSE ); // we are adding a file
1630 ni.setLength( f.audioProperties()->length() ); total_play_time += ni.Length();
1631 ni.setChannels( f.audioProperties()->channels() );
1632 ni.setSampleRate( f.audioProperties()->sampleRate() );
1633 ni.setBitRate( f.audioProperties()->bitrate() );
1634 /* Now, after we've filled our new catalog item - lets add it
1635 to the catalog list */
1636 MusicCatalog.append( ni );
1637 /* We said we will keep track of loaded files number - do so */
1638 loaded_files++; total_files++;
1641 /* Go to the next file in "dir-for-scan"... */
1642 ++it2;
1644 /* Now here is what we've got so far:
1645 o) new_dir - it is a pointer to the MmData item,
1646 containing the "dir-for-scan" directory info;
1647 o) ptr - a pointer the KListViewItem, which we created and added to the catalog tree;
1648 o) lf - contains the number of files added from all "dir-for-scan" SUBDIRs;
1649 o) loaded_files - contains the number of files added from "dir-for-scan" itself;
1650 If any of the last two in the list above is non-zero... */
1651 if ( loaded_files || lf )
1652 /* ...we must update the MmData item, containing the info for "dir-for-scan" to comply with this:
1653 [ ...if a MmData item holds info for a directory,
1654 its MmFileSize field holds a count of the files contained
1655 in it, !!! NOT !!! including its subdirs. ]
1656 We also update our overall folder counter.*/
1657 { (*new_dir).setFileSize( loaded_files ); (*new_dir).setLength( lf ); total_folders++; }
1658 /* If the sum of the last two is zero (0), or... lets put it this way -
1659 if both of them are zeros - then the first two ought to be removed; */
1660 else { MusicCatalog.remove( new_dir ); /* delete( ptr ); */ }
1661 /* We are done on this level - return subDlevel to where it was... */
1662 subDlevel--;
1663 if( (subDlevel == 0) && (s->dbg() & KMK_DBG_FS_OPS ) )
1664 kdDebug() << "traverse_tree: finishing MAIN traverse_tree..." << endl;
1665 /* And then return the sum of files in "dir-for-scan" and the files in
1666 its subdirectories.*/
1667 return (loaded_files + lf);
1671 * This private function starts by clearing current contents of
1672 * Tree KListView; next it goes trough kmk's MusicCatalog, recreating
1673 * the tree, described in the MmData structure, inside the Tree view
1675 void kmk::update_tree_view()
1677 long dirs = 0;
1678 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1679 kdDebug() << "update_tree_view: starting..." << endl;
1680 if( MusicCatalog.isEmpty() )
1682 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1683 kdDebug() << "update_tree_view: sorry, no data in catalog!" << endl;
1684 return;
1686 // clear current contents of treeListView
1687 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1688 kdDebug() << "update_tree_view: clearing contents of treeListView...." << endl;
1689 treeListView->clear();
1690 listViewRootItem = (KListViewItem*) treeListView;
1691 cur_vlItem = listViewRootItem;
1693 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1694 kdDebug() << "update_tree_view: cleared contents of treeListView!" << endl;
1695 // go through MusicCatalog...
1696 MmDataList::iterator it;
1697 KListViewItem *ptr;
1699 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1701 kdDebug()<<"******* SHOWING MUSIC CATALOG DATA SEQUENTLY ********"<<endl;
1702 for( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1704 kdDebug()<<"FileName(): "<<(*it).FileName()<<" "
1705 "Folder(): "<<(*it).Folder()<<endl;
1706 kdDebug()<<"Comment(): "<<(*it).Comment()<<" "
1707 "IsDir(): "<<(*it).IsDir()<<endl;
1708 kdDebug()<<"---------------------------"<<endl;
1710 kdDebug()<<"******** DONE MUSIC CATALOG DATA *************"<<endl;
1713 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1714 kdDebug() << "update_tree_view: setting all dirs to [NOT ADDED]..." << endl;
1715 // Go trough MusicCatalog and for each dir, set its BitRate() to 22
1716 // where non-zero value means that the dir is NOT in tree view and
1717 // and should be added at the correct place
1718 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1719 if ( (*it).IsDir() ) { (*it).setBitRate( 22 ); dirs++; }
1721 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1723 // look for a dir, that is NOT ADDED
1724 if ( (*it).IsDir() && ( (*it).BitRate() > 0 ) )
1726 // when found one, if its parent is "TREE_BASE" - add it to the top...
1727 if ( (*it).Comment().compare( "TREE_BASE" )==0 )
1729 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1730 kdDebug() << "update_tree_view: adding " << (*it).Folder()
1731 << " as a top-level TREE item..." << endl;
1732 ptr = new KListViewItem( (QListView*) treeListView, (*it).Folder() );
1733 // and reflect the fact that its added to tree view
1734 (*it).setBitRate( 0 );
1735 dirs--;
1736 it = MusicCatalog.begin();
1738 // and if its not - find the location where the new one has to be added
1739 else
1741 QListViewItemIterator trit( (QListView*) treeListView );
1742 while ( trit.current() )
1744 QListViewItem* lv = trit.current();
1745 // this one will hold the full-blown path to the last added folder
1746 QString folder_name;
1747 // this loop extracts a full path of the selected dir in the Tree view by
1748 // going from it to the top, collecting data on the way in *folder_name*
1749 while ( TRUE ) {
1750 folder_name.prepend( lv->text(0) );
1751 if( lv->parent() )
1753 if( lv->parent()->text(0).compare("/") != 0 ) folder_name.prepend( "/" );
1754 lv = lv->parent();
1756 // when the top is reached - the loop "break"s
1757 else break;
1759 if( (*it).Comment().compare( folder_name )==0 )
1761 if( s->dbg() & (KMK_DBG_CAT_MEM_OPS | KMK_DBG_OTHER ) )
1762 kdDebug() << "update_tree_view: adding " << (*it).FileName() << "." << endl;
1763 // and if it is - add it as a child of trit
1764 ptr = new KListViewItem( (QListViewItem*) trit.current(), (*it).FileName() );
1765 // and reflect the fact that its added to tree view
1766 (*it).setBitRate( 0 );
1767 dirs--;
1768 it = MusicCatalog.begin();
1769 break;
1771 ++trit;
1776 // check if we added all the dirs...
1777 if( dirs ) qWarning("Possible catalog incosistency detected!!! Better Re-Scan!!!");
1778 if( dirs && ( s->dbg() & KMK_DBG_CAT_MEM_OPS ) )
1779 kdDebug() << "update_tree_view: Possible catalog incosistency detected!!! Better Re-Scan!!!" << endl;
1780 // ok, we are done
1781 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1782 kdDebug() << "update_tree_view: done updating treeListView !" << endl;
1786 /* WARNING !!! OBSESIVE USE OF RECURSION!!!!
1788 #define TUT "traverseUpdate_tree<"<<subDlevel<<">: "
1789 unsigned long kmk::traverseUpdate_tree( const QString& dir )
1791 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
1792 { kmkSmooth->restart(); qApp->processEvents(); }
1793 /* Here we mark our tree depth - subDlevel is a kmk private var */
1794 subDlevel++;
1795 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1796 kdDebug() << TUT"starting with param [" << dir << "]..." << endl;
1797 /* Create a QDir object, set to the "dir-for-scan"
1798 The filter is set to DIRECTORIEs, without symlinks */
1799 QDir d ( dir );
1800 if( ! d.exists() )
1802 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1804 kdDebug() << TUT"starting folder does NOT EXIST on drive;" << endl;
1805 kdDebug() << TUT"removing it and all its children from catalog..." << endl;
1807 MmDataList::iterator sm_mm;
1808 QString qq1 = dir + "/";
1809 for ( sm_mm = MusicCatalog.begin(); sm_mm != MusicCatalog.end(); ++sm_mm )
1811 if ( ((*sm_mm).Folder().startsWith( qq1 ))||((*sm_mm).Folder().compare( dir )==0) )
1813 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1814 kdDebug() << TUT"REMOVING FROM CATALOG ["<<(*sm_mm).FileName()<<"], Folder()=["
1815 <<(*sm_mm).Folder()<<"]..."<<endl;
1816 MusicCatalog.remove( sm_mm );
1817 sm_mm = MusicCatalog.begin();
1818 tree_needs_update = TRUE;
1821 if(s->dbg() & KMK_DBG_FS_OPS )
1822 kdDebug() << TUT"finishing MAIN traverse_tree..." << endl;
1823 subDlevel--;
1824 return 0;
1826 d.setFilter( QDir::Dirs | QDir::NoSymLinks );
1827 /* This gets our "dir-for-scan" subdirs' in @p list */
1828 const QFileInfoList *list = d.entryInfoList();
1829 QFileInfoListIterator it( *list );
1830 /* @p fi is used to get the dir name and last modification time */
1831 QFileInfo *fi = new QFileInfo( d.absPath() );
1832 QString parent_dir = d.absPath();
1833 /* Create a new MmDataList entry with the extracted above data
1834 for the "dir-for-scan" and set @p new_dir to point at it;
1835 we need a pointer to this new item, because if we add any files,
1836 contained in this dir, or its subdirs, we must update its
1837 MmFileSize field according to this below... */
1838 MmDataList::iterator checked_mm_dir;
1840 bool mmdir_found = FALSE;
1841 bool dir_needs_update = FALSE;
1842 for ( checked_mm_dir = MusicCatalog.begin(); checked_mm_dir != MusicCatalog.end(); ++checked_mm_dir )
1844 if ( (*checked_mm_dir).IsDir() )
1846 if ( (*checked_mm_dir).Folder().compare( dir )==0 )
1848 mmdir_found = TRUE;
1849 if( fi->lastModified().toTime_t() != (*checked_mm_dir).ModifiedTime() )
1850 dir_needs_update = TRUE;
1851 break;
1856 if( mmdir_found )
1857 parent_dir = (*checked_mm_dir).Comment();
1858 else
1860 bool found = FALSE;
1861 MmDataList::iterator ckit;
1862 QString looked_for = dir;
1863 looked_for.setLength( looked_for.length() - ( d.dirName().length() + 1 ) );
1864 for ( ckit = MusicCatalog.begin(); ckit != MusicCatalog.end(); ++ckit ) {
1865 if ( (*ckit).IsDir() ) {
1866 if ( (*ckit).Folder().compare( looked_for )==0 ) { found = TRUE; break; }
1870 if(found) parent_dir = looked_for;
1871 else
1873 parent_dir = "TREE_BASE";
1874 checked_mm_dir = MusicCatalog.begin();
1878 if( s->dbg() & ( KMK_DBG_FS_OPS + KMK_DBG_CAT_MEM_OPS ) )
1880 kdDebug() << TUT"Checking STATUS of FOLDER [" << dir << "]..." << endl;
1881 kdDebug() << TUT"parent_dir set to : [" << parent_dir << "]." << endl;
1884 /* Some explanation : as we use the same elements to store info
1885 for audio files, and the dirs containing them - we do this trick:
1886 if the MmData item holds info on DIRECTORY - we set its fields like this:
1887 MmFolder - set to the ABSOLUTE DIR NAME
1888 MmFileName - set to the SHORT DIR NAME
1889 MmIsFolder - set to TRUE
1890 MmReadOnly - wether or not we have permission to write in DIR
1891 MmLength - set to the number of files in "dir-to-scan" subdirs
1892 MmFileSize - set to the number of files contained - updated later */
1893 MmData m = MmData( d.absPath(), // folder
1894 d.dirName(), // filename
1895 "", // artist
1896 "", // title
1897 "", // album
1898 "", // genre
1899 parent_dir, // comment
1900 0, // year
1901 0, // track number
1902 0, // file size - updated later
1903 fi->lastModified().toTime_t(), // modified time
1904 fi->isWritable(), // read only? - we may use this to determine if we can "rename" dirs
1905 TRUE, // is dir?
1906 0, // audio duration (length) in secs - upd. later
1907 0, // channels
1908 0, // samplerate
1909 0 ); // bitrate
1910 // If the dir is in catalog
1911 if( mmdir_found )
1913 // And needs update (its changed on drive)
1914 if( dir_needs_update )
1916 // Replace the outdated one with an actualized version
1917 // Some of the fields actually are not up-to-date - they get updated at the end
1918 if( checked_mm_dir==MusicCatalog.begin() )
1919 kdDebug()<<TUT"XXX CURRENT DIR IS AT LIST BEGINNING..."<<endl;
1921 checked_mm_dir = MusicCatalog.remove( checked_mm_dir );
1922 if( checked_mm_dir==MusicCatalog.begin() )
1923 kdDebug()<<TUT"XXX2 CURRENT DIR IS AT LIST BEGINNING..."<<endl;
1924 checked_mm_dir = MusicCatalog.insert( checked_mm_dir, m );
1925 tree_needs_update = TRUE;
1927 if( checked_mm_dir==MusicCatalog.begin() )
1928 kdDebug()<<TUT"XXX3 CURRENT DIR IS AT LIST BEGINNING..."<<endl;
1930 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1931 kdDebug() << TUT"IN catalog, NOT in SYNC" << endl;
1933 else
1935 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1936 kdDebug() << TUT"IN catalog, IN SYNC" << endl;
1939 // If the checked dir isn't in catalog - add it
1940 else
1942 for ( checked_mm_dir = MusicCatalog.begin(); checked_mm_dir != MusicCatalog.end(); ++checked_mm_dir )
1944 kdDebug()<<TUT"YYY Checking if "<<(*checked_mm_dir).IsDir()<<" is true and "
1945 <<(*checked_mm_dir).Folder()<<" is "<<parent_dir<<"..."<<endl;
1946 if ( ( (*checked_mm_dir).IsDir() ) && ( (*checked_mm_dir).Folder().compare( parent_dir )==0 ) )
1947 { if( checked_mm_dir!=MusicCatalog.end() ) checked_mm_dir++; break; }
1950 while( ( (( !(*checked_mm_dir).IsDir() )&&( (*checked_mm_dir).Folder().compare( parent_dir )==0 )) ||
1951 ((*checked_mm_dir).Folder().contains(parent_dir+"/")>=1) )&&(checked_mm_dir != MusicCatalog.end() ))
1952 checked_mm_dir++;
1954 if( checked_mm_dir==MusicCatalog.end() )
1955 kdDebug()<<TUT"YYY2 CURRENT DIR IS AT LIST END..."<<endl;
1957 kdDebug()<<TUT"YYY3 inserting "<<dir<<" in front of "<<(*checked_mm_dir).Folder()<<", "
1958 <<(*checked_mm_dir).FileName()<<endl;
1959 checked_mm_dir = MusicCatalog.insert( checked_mm_dir, m );
1960 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1961 kdDebug() << TUT"NOT IN catalog, adding it..." << endl;
1963 /* We are done with @p fi for now - so, get rid of it */
1964 delete fi;
1967 kdDebug()<<TUT"!!!! Now item's .Folder() shows "<<(*checked_mm_dir).Folder()<<endl;
1968 kdDebug()<<TUT"!!!! Now item's .FileName() shows "<<(*checked_mm_dir).FileName()<<endl;
1969 kdDebug()<<TUT"!!!! Now item's .IsDir() shows "<<(*checked_mm_dir).IsDir()<<endl;
1972 /* Here we initialize our subdirs' loaded files counter @p lf */
1973 unsigned long lf = 0;
1974 /* This checks if "dir-for-scan" has any subdirs */
1975 if ( !list->isEmpty() )
1977 /* And if it has, we go over the list of "dir-for-scan" subdirs */
1978 while ( (fi = it.current()) != 0 )
1980 /* Check if these subdirs are not the FS reserved "." and ".."
1981 FIX: AND that we can also access them !!! */
1982 if ( (fi->isReadable()) && (fi->isExecutable()) )
1983 if ( (fi->fileName().compare(".")!=0) && (fi->fileName().compare("..")!=0) )
1985 /* So, we've found a valid "dir-for-scan" subdirectory.
1986 If you remember - we used kmk's private var cur_vlItem as a parent
1987 of the KListViewItem we added earlier; so we want it to be
1988 "preset" when we are called, don't we? Let's make sure it is! */
1989 // cur_vlItem = ptr;
1990 /* We are setup and ready to call ourselves recursievely on this one.
1991 Because at the end of traverse_tree we provide a count of the
1992 actually added files to the catalog, including the added files
1993 from each subdir, lets increase @p lf with the numer of files in
1994 the directory we want to check...
1995 Here is the tracking : */
1996 lf += traverseUpdate_tree( d.filePath( fi->fileName() ) );
1998 /* Then go to the next in the list */
1999 ++it;
2001 /* We finished calling ourselves recursievly. So, if the subdirs'
2002 scan added any files - set the new catalog item to represent this
2003 accordingly - we hold subentries, so we need to be expandable;
2004 Maybe the user could set wheter or not the new catalog is already
2005 open or collapsed */
2008 // Now its time to remove folders, that are in catalog, but not on disk
2009 // Walk on trough all folders in *dir*...
2010 if ( !list->isEmpty() )
2012 MmDataList::iterator some_mm_dir;
2013 bool found;
2014 for ( some_mm_dir = MusicCatalog.begin(); some_mm_dir != MusicCatalog.end(); ++some_mm_dir )
2016 // Comment() field of MmData holds parent directory OR text "TREE_BASE" for folders
2017 if ( ( (*some_mm_dir).IsDir() ) && ( (*some_mm_dir).Comment().compare( dir )==0 ) )
2019 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2020 kdDebug() << TUT"Looking at [" << dir << "]'s subfolder..." << endl;
2022 found = FALSE;
2023 QFileInfoListIterator it3( *list );
2024 // And if it has, we go over the list of "dir-for-scan" subdirs
2025 while ( (fi = it3.current()) != 0 )
2027 // Check if these subdirs are not the FS reserved "." and ".."
2028 if ( (fi->isReadable()) && (fi->isExecutable()) )
2029 if ( (fi->fileName().compare(".")!=0) && (fi->fileName().compare("..")!=0) )
2031 /* if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2032 kdDebug() << TUT"Found REAL folder [" << fi->absFilePath() << "]." << endl;*/
2033 if( fi->absFilePath().compare( (*some_mm_dir).Folder() )==0 )
2035 // When we find one, which is in catalog - mark it
2036 found = TRUE;
2037 break;
2040 /* Go to the next file in "dir-for-scan"... */
2041 ++it3;
2044 /* if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2045 kdDebug() << TUT"Done listing." << endl;*/
2047 if( found==FALSE )
2049 QString pftd = (*some_mm_dir).Folder(); // pftd == Parent Folder To Delete
2050 QString pftd2 = pftd + "/";
2051 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2053 kdDebug() << TUT"Folder [" << pftd <<"] is in catalog ONLY!" << endl;
2054 kdDebug() << TUT"REMOVING it from catalog..." << endl;
2056 MusicCatalog.remove( some_mm_dir );
2057 tree_needs_update = TRUE;
2058 for ( some_mm_dir = MusicCatalog.begin(); some_mm_dir != MusicCatalog.end(); ++some_mm_dir )
2060 kdDebug() << TUT"Folder()=["<<(*some_mm_dir).Folder()<<"];"<<endl;
2061 // wipe out all items which happen to be children of *dir*
2062 //if ( ((*sm_mm).Folder().startsWith( dir ))&&((*sm_mm).Folder().contains( dir )>=1) )
2063 if( ((*some_mm_dir).Folder().compare( pftd )==0) || ((*some_mm_dir).Folder().startsWith( pftd2 )) )
2065 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2066 kdDebug() << TUT"REMOVING from CATALOG ["<<(*some_mm_dir).FileName()<<"], Folder()=["
2067 <<(*some_mm_dir).Folder()<<"]..."<<endl;
2068 MusicCatalog.remove( some_mm_dir );
2069 some_mm_dir = MusicCatalog.begin();
2070 tree_needs_update = TRUE;
2073 some_mm_dir = MusicCatalog.begin();
2075 else
2076 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2077 kdDebug() << TUT"Folder [" << (*some_mm_dir).FileName()
2078 << "] is a real a sub-folder of [" << dir << "]." << endl;
2085 MmDataList::iterator item_to_inset_at;
2086 // These few lines should get a pointer to first file inside *dir* - the new ones should be
2087 // added right on top of it
2088 for ( item_to_inset_at = MusicCatalog.begin(); item_to_inset_at != MusicCatalog.end(); ++item_to_inset_at )
2090 if ( ( !(*item_to_inset_at).IsDir() ) && ( (*item_to_inset_at).Folder().compare( dir )==0 ) )
2091 { if( item_to_inset_at!=MusicCatalog.end() ) item_to_inset_at++; break; }
2093 /* Then we set our "dir-for-scan" filter to files, without symlinks */
2094 d.setFilter( QDir::Files | QDir::NoSymLinks );
2095 /* And get a list of its contents, after the filtering is apllied */
2096 const QFileInfoList *list2 = d.entryInfoList();
2097 QFileInfoListIterator it2( *list2 );
2098 QFileInfo *fi2;
2099 /* These we will use for storage of the exctracted data from files */
2100 QString art, ttl, alb, gen, cmt;
2101 /* This will be our "dir-for-scan" ONLY (i.e. without including
2102 the count from the subdirs) loaded files counter; zero-init it.
2103 Nowadays there are file systems, capable of handling enormous amounts
2104 of files in a single directory - make sure we don't choke on someones
2105 huge collection, by using extra large counter.*/
2106 unsigned long loaded_files = 0;
2107 bool found_file = FALSE;
2108 bool file_needs_update = FALSE;
2109 MmDataList::iterator checked_mm_file;
2110 /* A cycle to iterate over the files in "dir-for-scan" */
2111 while ( (fi2 = it2.current()) != 0 )
2113 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
2114 { kmkSmooth->restart(); qApp->processEvents(); }
2115 /* If the files aren't with the supported extensions - ignore them;
2116 We call QFileInfo->extension(FALSE) because we need only chars after LAST '.' */
2117 if ( fi2->isReadable() )
2118 // LOSSLESS first
2119 if ( ( fi2->extension(FALSE).lower().compare("wv" ) == 0 ) || // wav-pack
2120 ( fi2->extension(FALSE).lower().compare("tta" ) == 0 ) || // true-audio
2121 ( fi2->extension(FALSE).lower().compare("ape" ) == 0 ) || // monkey's audio
2122 /* Is it correct for a FLAC file to have any other extension than "flac"?
2123 For example - can it be named "my_audio_file.fla"? Ideas, advices.... */
2124 ( fi2->extension(FALSE).lower().compare("flac") == 0 ) || // flac
2125 // LOSSY next
2126 ( fi2->extension(FALSE).lower().compare("mp3") == 0 ) || // mp3
2127 ( fi2->extension(FALSE).lower().compare("ogg") == 0 ) || // ogg-vorbis
2128 ( fi2->extension(FALSE).lower().compare("spx") == 0 ) || // ogg-speex
2129 ( fi2->extension(FALSE).lower().compare("mpc") == 0 ) ) // muse-pack
2131 found_file = FALSE;
2132 file_needs_update = FALSE;
2133 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2134 kdDebug() << TUT"Getting status of [" << fi2->fileName() << "]..." << endl;
2135 for ( checked_mm_file = MusicCatalog.begin(); checked_mm_file != MusicCatalog.end(); ++checked_mm_file )
2137 if ( (*checked_mm_file).IsDir()==FALSE )
2139 if ( ( (*checked_mm_file).Folder().compare( dir )==0 ) &&
2140 ( (*checked_mm_file).FileName().compare( fi2->fileName() )==0 ) )
2142 found_file = TRUE;
2143 if( fi2->lastModified().toTime_t() != (*checked_mm_file).ModifiedTime() )
2144 file_needs_update = TRUE;
2145 break;
2149 if( found_file )
2151 if( file_needs_update )
2153 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2155 kdDebug() << TUT"IN catalog, NOT in SYNC..." << endl;
2157 tree_needs_update = TRUE;
2158 // Removing items from MmData's structures posibly invalidates all iterator, pointing at the
2159 // deleted item, so we need to take care of that after the delete...
2160 MusicCatalog.remove( checked_mm_file );
2161 // These few lines should get a pointer to first file inside *dir* - the new ones should be
2162 // added right on top of it
2163 for ( item_to_inset_at = MusicCatalog.begin();
2164 item_to_inset_at != MusicCatalog.end(); ++item_to_inset_at )
2166 if ( ( !(*item_to_inset_at).IsDir() ) && ( (*item_to_inset_at).Folder().compare( dir )==0 ) )
2167 { if( item_to_inset_at!=MusicCatalog.end() ) item_to_inset_at++; break; }
2170 else
2172 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2173 kdDebug() << TUT"IN catalog, IN SYNC" << endl;
2174 loaded_files++;
2175 ++it2;
2176 continue;
2179 else if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2180 kdDebug() << TUT"NOT in catalog, ADDING it..." << endl;
2181 /* Init our storage for this file... */
2182 art = ""; ttl = ""; alb = ""; gen = ""; cmt = "";
2183 using namespace TagLib;
2184 /* Use TagLib's functions to extract meta data and audio properties;
2185 This tells TagLib to read tags, and be as fast as possible */
2186 if( s->dbg() & ( KMK_DBG_FS_OPS + KMK_DBG_OTHER ) )
2187 kdDebug() << TUT"Now checking: [" << d.filePath( fi2->fileName().latin1() ) << "]..." << endl;
2188 TagLib::FileRef f( d.filePath( fi2->fileName().latin1() ), TRUE, TagLib::AudioProperties::Fast );
2189 /* If TagLib found the file to be valid - then add it to the catalog.
2190 Maybe here would be a good place to update some global catalog stats,
2191 like total storage used by the collection we are cataloguining, number
2192 of files in it, average bitrate, highest one, lowest one, biggest file,
2193 smallest file... and anything else someone might consider "interesting"!*/
2194 if( f.file()->isValid() )
2196 TagLib::String s;
2197 s = f.tag()->artist(); art = s.toCString();
2198 s = f.tag()->title(); ttl = s.toCString();
2199 s = f.tag()->album(); alb = s.toCString();
2200 s = f.tag()->genre(); gen = s.toCString();
2201 s = f.tag()->comment(); cmt = s.toCString();
2202 /* Some of the extracted file data is in our storage vars now.
2203 Create a new MmData item, and fill it with what we've read. */
2204 MmData ni = MmData();
2205 ni.setFolder( d.absPath() );
2206 ni.setFileName( fi2->fileName() );
2207 ni.setArtist( art );
2208 ni.setTitle( ttl );
2209 ni.setAlbum( alb );
2210 ni.setGenre( gen );
2211 ni.setComment( cmt );
2212 ni.setYear( f.tag()->year() );
2213 ni.setTrackNum( f.tag()->track() );
2214 ni.setFileSize( fi2->size() );
2215 // qDebug( "To "+QString::number( total_bytes )+" B, adding "+QString::number((Q_ULLONG) fi2->size())+" B." );
2216 total_bytes += (Q_ULLONG) fi2->size();
2217 ni.setModifiedTime( fi2->lastModified().toTime_t() );
2218 /* How about using Qt's functions QFile and QDir to check this?
2219 Not a problem! I've checked it - TagLib gives correct data; in a bunch of mp3s, I
2220 set to 2 of them to have only read acces - TagLib said exactly this! The only thing
2221 left in mind is speed, but... how do I check IT? Pl.Petrov */
2222 ni.setIsReadOnly( f.file()->readOnly() );
2223 ni.setIsDir( FALSE ); // we are adding a file
2224 ni.setLength( f.audioProperties()->length() ); total_play_time += ni.Length();
2225 ni.setChannels( f.audioProperties()->channels() );
2226 ni.setSampleRate( f.audioProperties()->sampleRate() );
2227 ni.setBitRate( f.audioProperties()->bitrate() );
2228 /* Now, after we've filled our new catalog item - lets add it
2229 to the catalog list */
2230 item_to_inset_at = MusicCatalog.insert( item_to_inset_at, ni );
2231 tree_needs_update = TRUE;
2232 /* We said we will keep track of loaded files number - do so */
2233 loaded_files++; total_files++;
2236 /* Go to the next file in "dir-for-scan"... */
2237 ++it2;
2240 // Now its time to remove files, that are in catalog, but not on disk
2241 // Walk on trough all files in *dir*...
2242 for ( checked_mm_file = MusicCatalog.begin(); checked_mm_file != MusicCatalog.end(); ++checked_mm_file )
2244 if ( ( (*checked_mm_file).IsDir()==FALSE ) && ( (*checked_mm_file).Folder().compare( dir )==0 ) )
2246 found_file = FALSE;
2247 QFileInfoListIterator it3( *list2 );
2248 while ( (fi2 = it3.current()) != 0 )
2250 /* If the files aren't with the supported extensions - ignore them;
2251 We call QFileInfo->extension(FALSE) because we need only chars after LAST '.' */
2252 if ( fi2->isReadable() )
2253 // LOSSLESS first
2254 if ( ( fi2->extension(FALSE).lower().compare("wv" ) == 0 ) || // wav-pack
2255 ( fi2->extension(FALSE).lower().compare("tta" ) == 0 ) || // true-audio
2256 ( fi2->extension(FALSE).lower().compare("ape" ) == 0 ) || // monkey's audio
2257 /* Is it correct for a FLAC file to have any other extension than "flac"?
2258 For example - can it be named "my_audio_file.fla"? Ideas, advices.... */
2259 ( fi2->extension(FALSE).lower().compare("flac") == 0 ) || // flac
2260 // LOSSY next
2261 ( fi2->extension(FALSE).lower().compare("mp3") == 0 ) || // mp3
2262 ( fi2->extension(FALSE).lower().compare("ogg") == 0 ) || // ogg-vorbis
2263 ( fi2->extension(FALSE).lower().compare("spx") == 0 ) || // ogg-speex
2264 ( fi2->extension(FALSE).lower().compare("mpc") == 0 ) ) // muse-pack
2266 if( fi2->fileName().compare( (*checked_mm_file).FileName() )==0 )
2268 // When we find one, which is in catalog - mark it
2269 found_file = TRUE;
2270 break;
2273 /* Go to the next file in "dir-for-scan"... */
2274 ++it3;
2276 // If the file *(*checked_mm_file)* is in catalog, but not in the list of file of *dir*
2277 // - then its for DELETION from catalog
2278 if( found_file==FALSE )
2280 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2281 kdDebug() << TUT"REMOVING [" << (*checked_mm_file).FileName()
2282 << "] from catalog..." << endl;
2283 tree_needs_update = TRUE;
2284 MusicCatalog.remove( checked_mm_file );
2285 checked_mm_file = MusicCatalog.begin();
2290 /* Now here is what we've got so far:
2291 o) new_dir - it is a pointer to the MmData item,
2292 containing the "dir-for-scan" directory info;
2293 o) ptr - a pointer the KListViewItem, which we created and added to the catalog tree;
2294 o) lf - contains the number of files added from all "dir-for-scan" SUBDIRs;
2295 o) loaded_files - contains the number of files added from "dir-for-scan" itself;
2296 If any of the last two in the list above is non-zero... */
2297 if ( loaded_files || lf )
2298 /* ...we must update the MmData item, containing the info for "dir-for-scan" to comply with this:
2299 [ ...if a MmData item holds info for a directory,
2300 its MmFileSize field holds a count of the files contained
2301 in it, !!! NOT !!! including its subdirs. ]
2302 We also update our overall folder counter.*/
2303 { (*checked_mm_dir).setFileSize( loaded_files ); (*checked_mm_dir).setLength( lf ); total_folders++; }
2304 /* If the sum of the last two is zero (0), or... lets put it this way -
2305 if both of them are zeros - then the first two ought to be removed; */
2306 else
2308 MusicCatalog.remove( checked_mm_dir );
2309 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2310 kdDebug() << TUT"REMOVING FOLDER [" << (*checked_mm_dir).FileName() << "] from catalog..." << endl;
2312 /* We are done on this level - return subDlevel to where it was... */
2313 if(s->dbg() & KMK_DBG_FS_OPS )
2314 if(subDlevel != 1) kdDebug() << TUT"done!" << endl;
2315 else kdDebug() << TUT"Finishing MAIN traverse_tree..." << endl;
2316 subDlevel--;
2317 /* And then return the sum of files in "dir-for-scan" and the files in
2318 its subdirectories.*/
2319 return (loaded_files + lf);
2323 /* WARNING !!! OBSESIVE USE OF RECURSION!!!!
2325 void kmk::bytes_to_read_by_traverse( const QString& dir )
2327 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
2328 { kmkSmooth->restart(); qApp->processEvents(); }
2329 // kdDebug() << "called with param " << dir << endl;
2330 QDir d ( dir ); d.setFilter( QDir::Dirs | QDir::NoSymLinks );
2331 const QFileInfoList *list = d.entryInfoList(); QFileInfoListIterator it( *list );
2332 QFileInfo *fi = new QFileInfo();
2333 if ( !list->isEmpty() )
2334 while ( (fi = it.current()) != 0 )
2336 if ( (fi->isReadable()) && (fi->isExecutable()) )
2337 if ( (fi->fileName().compare(".")!=0) && (fi->fileName().compare("..")!=0) )
2339 if ( !fi->isDir() ) kdDebug() << "...and we are trying to do dir op on non-dir!" << endl;
2340 else bytes_to_read_by_traverse( d.filePath( fi->fileName() ) );
2342 ++it;
2344 d.setFilter( QDir::Files | QDir::NoSymLinks ); const QFileInfoList *list2 = d.entryInfoList();
2345 QFileInfoListIterator it2( *list2 ); QFileInfo *fi2;
2346 while ( (fi2 = it2.current()) != 0 )
2348 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
2349 { kmkSmooth->restart(); qApp->processEvents(); }
2350 if ( fi2->isReadable() )
2351 if ( ( fi2->extension(FALSE).lower().compare("mp3") == 0 ) ||
2352 ( fi2->extension(FALSE).lower().compare("ogg") == 0 ) ||
2353 ( fi2->extension(FALSE).lower().compare("flac") == 0 ) )
2354 { bytes_to_read += (Q_ULLONG) fi2->size(); files_to_read++; }
2355 ++it2;
2359 /* Some neat code I am really proud of: fast and effective; doesn't tollerate storage errors, though... */
2360 uint kmk::generateListViewSubtreeXML( const KListViewItem *item, QDomDocument doc, QDomElement e, const uint add_lv )
2362 if( item != 0 )
2364 // kdDebug() << "DOMelem <tree_item Name=\"" << item->text(0) <<"\" M=\"" << add_lv << "\" />" << endl;
2366 QDomElement dscr =
2367 doc.createElement( "FolderTreeDescriptor" );
2368 dscr.setAttribute( "FolderName", item->text(0) );
2369 dscr.setAttribute( "GoUpHowMuch", add_lv );
2370 e.appendChild( dscr );
2372 uint levels_added=0;
2373 KListViewItem * some_child = (KListViewItem*) item->firstChild();
2374 while( some_child )
2376 levels_added = generateListViewSubtreeXML( some_child, doc, e, levels_added );
2377 some_child = (KListViewItem*) some_child->nextSibling();
2380 return ++levels_added;
2382 else return 0;
2385 void kmk::generateListViewXML( const KListView *list, QDomDocument doc, QDomElement e )
2387 if( list != 0 )
2389 uint levels_added=0;
2390 KListViewItem * some_child = (KListViewItem*) list->firstChild();
2391 while( some_child )
2393 levels_added = generateListViewSubtreeXML( some_child, doc, e, levels_added );
2394 some_child = (KListViewItem*) some_child->nextSibling();
2401 * Finds all files in TreeList currentItem and all its subfolders
2402 * and passes them to player_bin with parameter @param act
2403 * NOTE this function respects _kmk_include_subdirs
2404 * NOTE 2: in a rethink of what the function should do,
2405 * the new behaviour is that it writes all the relevant files
2406 * in a .M3U or .PLS playlist file (stored with a randomly generated
2407 * name in /tmp/kmk) and passes it to the player with @p act
2409 void kmk::playerDir( uint act ) // act>=1 - play; act==0 - enqueue
2411 QListViewItem* lv = treeListView->currentItem();
2412 if ( lv != 0 )
2414 // generate some random based filename for the playlist file
2415 QString flNm = "/tmp/kmk/kmk_lst_"+QString::number( random() )+".m3u";
2416 QFile file( flNm );
2417 if ( file.open( IO_WriteOnly ) )
2419 QTextStream stream( &file );
2420 stream << "#EXTM3U\n";
2421 // determine the folder we use as root and save it in @p d
2422 QString d;
2423 while ( TRUE ) {
2424 d.prepend( lv->text(0) );
2425 if( lv->parent() ) { if( lv->parent()->text(0).compare("/") != 0 ) d.prepend( "/" ); lv = lv->parent(); }
2426 else break;
2428 // do the acctual adding of files to the playlist....
2429 MmDataList::iterator it;
2430 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
2431 if ( _kmk_include_subdirs )
2433 if ( ( !((*it).IsDir()) ) && ( (( (*it).Folder().compare(d)==0 )) || ( (*it).Folder().contains(d+"/",TRUE)==1 ) ) )
2435 stream << "#EXTINF:" << (*it).Length() << " ," << (*it).Artist() << " - " << (*it).Title() << "\n";
2436 stream << (*it).Folder()+"/"+(*it).FileName() << "\n";
2439 else
2441 if ( ( !((*it).IsDir()) ) && ( (*it).Folder().compare(d)==0 ) )
2443 stream << "#EXTINF:" << (*it).Length() << " ," << (*it).Artist() << " - " << (*it).Title() << "\n";
2444 stream << (*it).Folder()+"/"+(*it).FileName()<<"\n";
2447 file.close();
2449 if (act) player->playPlaylist( flNm );
2450 else player->addPlaylist( flNm );
2451 // file.remove();
2453 else QMessageBox::warning( this, i18n("KDE Music Kataloger"),i18n("Could not open %1 for writing!").arg(flNm),
2454 QMessageBox::Abort,QMessageBox::NoButton,QMessageBox::NoButton );
2458 MmData* kmk::findMmData( const QString& folder, const QString& filename )
2460 bool found = FALSE;
2461 MmDataList::iterator it;
2462 for( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
2464 if( ((*it).Folder().compare(folder)==0) && ((*it).FileName().compare(filename)==0) )
2466 found = TRUE;
2467 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2468 kdDebug() << "findMmData: called once. " << endl;
2469 break;
2472 if( found ) return &(*it);
2473 else return 0;
2476 const QString kmk::formatted_string_from_seconds( const Q_ULLONG t )
2478 QString ttime = "";
2479 unsigned short secs, mins, hours, years;
2480 secs = mins = hours = years = 0;
2481 uint days = 0;
2483 if ( t == 0 ) return ttime;
2484 years = t / 30758400;
2485 days = (t - (years * 30758400)) / 86400;
2486 hours = (t - ((years * 30758400) + (days * 86400))) / 3600;
2487 mins = (t - ((years * 30758400) + (days * 86400) + (hours * 3600))) / 60;
2488 secs = t % 60;
2489 if ( years ) if ( years > 1 ) ttime.append( i18n("%1 years").arg(years) );
2490 else ttime.append( i18n("1 year") );
2491 if ( days )
2493 if ( years ) ttime.append(", ");
2494 if ( days > 1 ) ttime.append( i18n("%1 days").arg(days) );
2495 else ttime.append( i18n("1 day") );
2497 if ( (days || years) && (hours || mins || secs) ) ttime.append(", ");
2498 if ( hours )
2500 if ( hours>9 ) ttime.append( QString("%1:").arg(hours) );
2501 else ttime.append( QString("0%1:").arg(hours) );
2503 if ( secs || mins || hours )
2505 if ( mins>9 ) ttime.append( QString("%1:").arg(mins) );
2506 else ttime.append( QString("0%1:").arg(mins) );
2508 if ( secs || mins || hours )
2510 if ( secs>9 ) ttime.append( QString("%1").arg(secs) );
2511 else ttime.append( QString("0%1").arg(secs) );
2513 return ttime;
2516 const bool kmk::catalog_has_dir( const QString & looked_for )
2518 bool found = FALSE;
2519 MmDataList::iterator it;
2521 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2522 kdDebug() << "catalog_has_dir: Now will look for dir [" << looked_for << "]..." << endl;
2523 // QDir gives us paths ending on "/" and in the catalog - folders don't
2524 // end on "/"; so, strip that last slash "/" symbol
2525 QString stripped_looked_for = looked_for;
2526 if ( stripped_looked_for.length()>1 )
2527 stripped_looked_for.setLength( stripped_looked_for.length() - 1 );
2528 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
2530 if ( (*it).IsDir() )
2532 if ( (*it).Folder().compare( stripped_looked_for )==0 )
2534 found = TRUE;
2535 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2536 kdDebug() << "catalog_has_dir: ...found! " << endl;
2537 break;
2542 return found;
2545 void kmk::loadCatalog( const QString & fileName )
2547 QDomDocument doc = QDomDocument::QDomDocument();
2548 QFile file( fileName );
2549 if ( !file.open( IO_ReadOnly | IO_Raw ) ) {
2550 QMessageBox::warning( this, i18n("KDE Music Kataloger"),
2551 i18n("Could not open %1 for reading!").arg(fileName),
2552 QMessageBox::Abort,QMessageBox::NoButton,QMessageBox::NoButton );
2553 return;
2555 this->setCaption( i18n("Checking integrity of [%1] - ").arg(fileName) );
2556 this->repaint(); qApp->processEvents();
2557 // sleep( 5 ); kdDebug() << " sleeping..." << endl;
2558 QString xml_parse_err = "test"; int err_ln = 0; int err_cl = 0;
2559 QTime parse_time; parse_time.start();
2560 if ( !doc.setContent( &file, TRUE, &xml_parse_err, &err_ln, &err_cl ) ) {
2561 file.close();
2562 // qWarning( QString::number( *err_ln )+ " " + QString::number( *err_cl ) );
2563 QMessageBox::warning( this, i18n("KDE Music Kataloger"),
2564 i18n("Error parsing catalog file %1 !\n MSG: %2 on line %3, column %4.")
2565 .arg(fileName).arg(xml_parse_err.ascii()).arg(QString::number( err_ln )).arg(QString::number( err_cl )),
2566 QMessageBox::Abort,QMessageBox::NoButton,QMessageBox::NoButton );
2567 this->setCaption( savedCaption );
2568 return;
2570 file.close();
2571 if( s->dbg() & KMK_DBG_CATALOG_IO )
2572 kdDebug() << "loadCatalog: parse_file(SAX2 parse): elapsed time: "
2573 << parse_time.elapsed() << " ms." << endl;
2574 parse_time.restart();
2575 clearCatalogData();
2576 this->setCaption( i18n("Loading [%1]...").arg(fileName) );
2577 catalogFileName = fileName;
2578 qApp->processEvents();
2580 /** READ CATALOG VERSION INFO - if none is found - inform, and bail out */
2581 bool process = TRUE;
2582 ulong nodes_to_add = 0;
2583 QDomNodeList ml = doc.elementsByTagName( "KMK_catalog_file_data" ); QDomNode n; QDomElement e; QDomAttr a;
2584 for ( uint i = 0; i<ml.count(); i++)
2586 n = ml.item( i );
2587 if ( n.isElement() )
2588 { e = n.toElement();
2589 QString tx = e.attribute("Version");
2590 if( !tx.isNull() )
2592 if( tx.toInt() > 1 ) KMessageBox::sorry( this, i18n("The catalog [%1] is saved in new format (v.%2),"
2593 "so some data will not be recognized.").arg(fileName).arg(tx.toInt()),i18n("KDE Music Kataloger") );
2594 if( tx.toInt() == 0 ) process = FALSE;
2598 if( s->dbg() & KMK_DBG_CATALOG_IO )
2599 kdDebug() << "loadCatalog: parse_file(new read): catalog version read time: "
2600 << parse_time.elapsed() << " ms." << endl;
2601 parse_time.restart();
2602 if( process )
2604 ml = doc.elementsByTagName( "CatalogFile_Statistics" );
2605 for ( uint i = 0; i<ml.count(); i++)
2607 n = ml.item( i );
2608 if ( n.isElement() )
2609 { e = n.toElement();
2610 QString tx = e.attribute("Total_files_count");
2611 if( !tx.isNull() ) total_files = (Q_ULLONG) tx.toDouble();
2612 tx = e.attribute("Total_bytes_count");
2613 if( !tx.isNull() ) total_bytes = (Q_ULLONG) tx.toDouble();
2614 tx = e.attribute("Total_dirs_count");
2615 if( !tx.isNull() ) total_folders = (Q_ULLONG) tx.toDouble();
2616 tx = e.attribute("Total_secs_count");
2617 if( !tx.isNull() ) total_play_time = (Q_ULLONG) tx.toDouble();
2618 tx = e.attribute("Average_file_size");
2619 if( !tx.isNull() ) average_file_size = (Q_ULLONG) tx.toDouble();
2622 if( s->dbg() & KMK_DBG_CATALOG_IO )
2623 kdDebug() << "loadCatalog: parse_file(new read): catalog stats read time: "
2624 << parse_time.elapsed() << " ms." << endl;
2625 parse_time.restart();
2628 /** READ THE FOLDERS STRUCTURE AUXILARY DATA - version, counters, etc */
2629 /* Currently - IRRELEVANT
2630 ml = doc.elementsByTagName( "CatalogFile_TreeDescription" );
2631 for ( uint i = 0; i<ml.count(); i++)
2633 n = ml.item( i );
2634 if ( n.isAttr() )
2635 { a = n.toAttr();
2636 #ifdef __KMK_DEBUG
2637 kdDebug() << "(kmk): i="<<i<<"; found attr; name: "<< a.name()<<"; value: "<< a.value() << endl;
2638 #endif
2640 if ( n.isElement() )
2641 { e = n.toElement();
2642 #ifdef __KMK_DEBUG
2643 kdDebug() << "(kmk): i="<<i<<"; found elem; tag: "<< e.tagName() << endl;
2644 #endif
2649 /** READ THE FOLDERS STRUCTURE AND RECREATE IT IN KLISTVIEW */
2650 /* if( process )
2652 KListViewItem *CURRENT = 0; bool warned = FALSE;
2653 ml = doc.elementsByTagName( "FolderTreeDescriptor" );
2654 for ( uint i = 0; i<ml.count(); i++)
2656 n = ml.item( i );
2657 if ( n.isElement() )
2658 { e = n.toElement();
2659 QString nm = e.attribute("FolderName");
2660 if( nm.isNull() ) kdDebug() << "READ A NULL FOLDER NAME!" << endl;
2661 QString up = e.attribute("GoUpHowMuch");
2662 if( up.isNull() ) kdDebug() << "READ A NULL GO_UP_HOW_MUCH!"<< endl;
2663 uint t = up.toUInt();
2664 while( (t) && (CURRENT) )
2666 CURRENT = (KListViewItem*)CURRENT->parent();
2667 t--;
2669 if( t && (!warned) ) { KMessageBox::sorry( this,
2670 i18n("The catalog file you are trying to load is messed up. "
2671 "You get this message, so it passed XML sanity checks. That means almost for sure "
2672 "that it has been edited by hand - you are better off if you recreate it via Catalog->New. "
2673 "The scanning is fast enough anyway (around 10GB mp3 files scanned per minute)!\n"
2674 "You have been warned - don't complain if something goes wrong."),
2675 i18n("KDE Music Kataloger") );
2676 warned = TRUE; }
2677 if( s->dbg() & KMK_DBG_OTHER )
2678 if(CURRENT)
2679 kdDebug() << "loadCatalog: " << i << ":(t="<<t<<") adding "<<nm<<" at "<<CURRENT->text(0)<<endl;
2680 else
2681 kdDebug() << "loadCatalog: " << i << ":(t="<<t<<") adding "<<nm<<" at ROOT."<<endl;
2682 if(CURRENT) CURRENT = new KListViewItem( (QListViewItem*) CURRENT, nm );
2683 else CURRENT = new KListViewItem( (QListView*) treeListView, nm );
2684 //kdDebug() << i << ":-------------> "<<CURRENT->text(0)<<"'s parent() is: " << CURRENT->parent() << endl;
2687 if( s->dbg() & KMK_DBG_CATALOG_IO )
2688 kdDebug() << "loadCatalog: parse_file(new read): folder tree reconstruction time: "
2689 << parse_time.elapsed() << " ms." << endl;
2690 parse_time.restart();
2691 qApp->processEvents();
2693 // 10 February 2008 - removed folder tree reconstruction;
2694 // whoever needs it, now should call update_tree_view() instead
2696 /** READ CATALOG ITEMS AUXILARY DATA - objects number, size, etc */
2697 if( process )
2699 ml = doc.elementsByTagName( "CatalogFile_Objects" );
2700 for ( uint i = 0; i<ml.count(); i++)
2702 n = ml.item( i );
2703 if ( n.isElement() )
2704 { e = n.toElement();
2705 QString tx = e.attribute("Total_MObjs_count");
2706 if( !tx.isNull() ) nodes_to_add = tx.toULong();
2709 if( s->dbg() & KMK_DBG_CATALOG_IO )
2710 kdDebug() << "loadCatalog: parse_file(new read): catalog objects count read time: "
2711 << parse_time.elapsed() << " ms." << endl;
2712 parse_time.restart();
2715 // some vars used to convert old dirs data to new - fill in Comment field of MmData
2716 QString base_dir = "";
2717 long dirs_found = 0;
2718 /** READ ACTUAL AUDIO FILE DATA INTO MusicCatalog */
2719 if( process )
2721 ml = doc.elementsByTagName( "MObj" ); MmData node; uint tags_found;
2722 for ( uint i = 0; i<ml.count(); i++)
2724 tags_found = 0;
2725 n = ml.item( i );
2726 if ( n.isElement() )
2727 { e = n.toElement(); QString
2728 tx = e.attribute("Folder"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2729 else { node.setFolder( tx ); tags_found++; }
2730 tx = e.attribute("Filename"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2731 else { node.setFileName( tx ); tags_found++; }
2732 tx = e.attribute("Artist"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2733 else { node.setArtist( tx ); tags_found++; }
2734 tx = e.attribute("Title"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2735 else { node.setTitle( tx ); tags_found++; }
2736 tx = e.attribute("Album"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2737 else { node.setAlbum( tx ); tags_found++; }
2738 tx = e.attribute("Genre"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2739 else { node.setGenre( tx ); tags_found++; }
2740 tx = e.attribute("Comment"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2741 else { node.setComment( tx ); tags_found++; }
2742 tx = e.attribute("Year"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2743 else { node.setYear( tx.toInt() ); tags_found++; }
2744 tx = e.attribute("TrackNumber"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2745 else { node.setTrackNum( tx.toInt() ); tags_found++; }
2746 tx = e.attribute("Length"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2747 else { node.setLength( tx.toInt() ); tags_found++; }
2748 tx = e.attribute("Modified"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2749 else { node.setModifiedTime( tx.toInt() ); tags_found++; }
2750 tx = e.attribute("Size"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2751 else { node.setFileSize( tx.toInt() ); tags_found++; }
2752 tx = e.attribute("Channels"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2753 else { node.setChannels( tx.toInt() ); tags_found++; }
2754 tx = e.attribute("BitRate"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2755 else { node.setBitRate( tx.toInt() ); tags_found++; }
2756 tx = e.attribute("SampleRate"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2757 else { node.setSampleRate( tx.toInt() ); tags_found++; }
2758 tx = e.attribute("IsReadOnly"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2759 else { node.setIsReadOnly( (tx.compare("YES")==0) ? TRUE:FALSE ); tags_found++; }
2760 tx = e.attribute("IsFolder"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2761 else { node.setIsDir( (tx.compare("YES")==0) ? TRUE:FALSE ); tags_found++; }
2763 if ( tags_found )
2765 qApp->processEvents();
2766 if( node.IsDir() )
2767 if( node.Comment().isEmpty() )
2769 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2770 kdDebug() << "loadCatalog: converting old-format folder "
2771 << node.Folder() << "..." << endl;
2772 dirs_found++;
2773 if( dirs_found == 1)
2775 base_dir = node.Folder();
2776 node.setComment( "TREE_BASE" );
2777 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2778 kdDebug() << "loadCatalog: adding folder comment [" << base_dir
2779 << "]; setting it as BASE_DIR..." << endl;
2781 else
2783 if( node.Folder().contains( base_dir ) > 0 )
2785 QString tmpQ = node.Folder();
2786 tmpQ.setLength( node.Folder().length() - ( node.FileName().length() + 1 ) );
2787 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2788 kdDebug() << "loadCatalog: adding folder comment [" << tmpQ
2789 << "] as part of conversion..." << endl;
2790 node.setComment( tmpQ );
2792 else
2794 base_dir = node.Folder();
2795 node.setComment( "TREE_BASE" );
2796 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2797 kdDebug() << "loadCatalog: adding folder comment [" << base_dir
2798 << "]; setting it as NEW BASE_DIR..." << endl;
2802 MusicCatalog.append( node );
2803 if( s->dbg() & KMK_DBG_OTHER )
2804 kdDebug() << "loadCatalog: parse_file(new read): just added " << i << "-th object;" << endl;
2807 if( s->dbg() & KMK_DBG_CATALOG_IO )
2808 kdDebug() << "loadCatalog: parse_file(new read): node read and add time: "
2809 << parse_time.elapsed() << " ms." << endl;
2810 parse_time.restart();
2813 if( ! process )
2814 KMessageBox::sorry( this, i18n("No catalog markings found in [%1].\n"
2815 "This can happen if the file is corrupt.")
2816 .arg(catalogFileName),i18n("KDE Music Kataloger") );
2817 else {
2818 if( dirs_found ) setCatalogStateAndUpdate( Modified );
2819 else setCatalogStateAndUpdate( Saved );
2820 fileCatalogFileStats->setEnabled( TRUE );
2821 fileCatalogFileClose->setEnabled( TRUE );
2826 #include "kmk.moc"