runtime debug infrastructure
[kmk.git] / src / kmk.cpp
blob9a17858ac4460b81bfaaa4ac36f1ba370aae544e
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 #undef __KMK_DEBUG
51 const QString player_bin = "xmms";
53 kmk::kmk()
54 : KMainWindow( 0, i18n("KDE Music Kataloger") )
56 // allocate settings object
57 d = new KmkGlobalSettings;
58 // be safe here
59 Q_CHECK_PTR( d );
60 if ( !d ) kdDebug() << "no mem for d!" << endl;
61 // if all is fine - then read the settings
62 d->readSettings( kapp->config() );
63 // save the caption - this one will be used when we need to restore it
64 savedCaption = this->caption();
65 init_interface();
67 // set config object to read GUI group
68 kapp->config()->setGroup( "GUI" );
69 // reads the splitter settings from the config object
70 splitter1->setSizes( kapp->config()->readIntListEntry( "splitter config" ) );
71 // then moves the window to its last position
72 move( kapp->config()->readPointEntry( "main win position" ) );
74 kmkSmooth = new QTime();
75 kmkProgress = new kmk_progress_disp();
76 kmkProgressTimer = new QTimer();
78 KPopupMenu * file = new KPopupMenu( this );
79 // Actions
80 fileNewAction = KStdAction::openNew(this,
81 SLOT(slotFileNew()),actionCollection());
82 fileOpenAction = KStdAction::open(this,
83 SLOT(slotFileOpen()),actionCollection());
84 catalogAddNewFolderAction = new KAction( i18n("Add new folder to catalog"), KShortcut(""), this,
85 SLOT(slotCatalogAddNewFolder()), actionCollection(), "add_new_folder" );
86 fileSaveAction = KStdAction::save(this,
87 SLOT(slotFileSave()),actionCollection());
88 fileSaveAsAction = KStdAction::saveAs(this,
89 SLOT(slotFileSaveAs()),actionCollection());
90 fileCatalogFileStats = new KAction( i18n("Catalog statistics"), KShortcut(""), this,
91 SLOT(slotCatalogFileStats()), actionCollection(), "catalog_stats" );
92 fileCatalogFileClose = KStdAction::close(this,
93 SLOT(slotCatalogFileClose()),actionCollection());
94 fileQuitAction = KStdAction::quit(this,
95 SLOT(slotFileQuit()),actionCollection());
96 listTableToggleTitleAction = new KAction( i18n("Toggle Title"), KShortcut(""), this,
97 SLOT(slotListTableToggleTitle()), actionCollection(), "toggle_title" );
98 TagEditAction = new KAction( i18n("Tag editor"), KShortcut(""), this,
99 SLOT(slotTagEdit()), actionCollection(), "tag_editor" );
100 listTableLocateAction = new KAction( i18n("Locate"), KShortcut(""), this,
101 SLOT(slotLocateRequested()), actionCollection(), "locate" );
102 programSettingsAction = KStdAction::preferences(this,
103 SLOT(slotProgramSettings()),actionCollection());
104 playerPreviousAction = new KAction( i18n("Previous "), KShortcut("ALT+Z"), this,
105 SLOT(slotPlayerPrevious()), actionCollection(), "prev");
106 playerPlayAction = new KAction(i18n("Play "), KShortcut("ALT+X"), this,
107 SLOT(slotPlayerPlay()), actionCollection(), "play");
108 playerPauseAction = new KAction(i18n("Pause "), KShortcut("ALT+C"), this,
109 SLOT(slotPlayerPause()), actionCollection(), "pause");
110 playerStopAction = new KAction(i18n("Stop "), KShortcut("ALT+V"), this,
111 SLOT(slotPlayerStop()), actionCollection(), "stop");
112 playerNextAction = new KAction(i18n("Next "), KShortcut("ALT+B"), this,
113 SLOT(slotPlayerNext()), actionCollection(), "next");
114 playerEnqueueAction = new KAction(i18n("Enqueue selection "), KShortcut(""), this,
115 SLOT(slotPlayerEnqueue()), actionCollection(), "enqueue");
116 playerEnqueueDirAction = new KAction(i18n("Enqueue selected folder ONLY "), KShortcut(""), this,
117 SLOT(slotPlayerEnqueueDir()), actionCollection(), "enqueue_dir");
118 playerEnqueueDirSubdirsAction = new KAction(i18n("Enqueue folder with subfolders "), KShortcut(""), this,
119 SLOT(slotPlayerEnqueueDirSubdirs()), actionCollection(), "enqueue_dir_subdirs");
120 playerPlayDirAction = new KAction(i18n("Play selected folder ONLY "), KShortcut(""), this,
121 SLOT(slotPlayerPlayDir()), actionCollection(), "play_dir");
122 playerPlayDirSubdirsAction = new KAction(i18n("Play folder with subfolders "), KShortcut(""), this,
123 SLOT(slotPlayerPlayDirSubdirs()), actionCollection(), "play_dir_subdirs");
124 playerPlaySelectionAction = new KAction(i18n("Play selection "), KShortcut(""), this,
125 SLOT(slotPlayerPlaySelection()), actionCollection(), "play_sel");
126 // File menu actions
127 fileNewAction->plug( file );
128 fileOpenAction->plug( file );
129 // catalogAddNewFolderAction->setEnabled( FALSE );
130 catalogAddNewFolderAction->plug( file );
131 fileSaveAction->setEnabled( FALSE );
132 fileSaveAction->plug( file );
133 fileSaveAsAction->setEnabled( FALSE );
134 fileSaveAsAction->plug( file );
135 fileCatalogFileStats->setEnabled( FALSE );
136 fileCatalogFileStats->plug( file );
137 fileCatalogFileClose->setEnabled( FALSE );
138 fileCatalogFileClose->plug( file );
139 file->insertSeparator();
140 fileQuitAction->plug( file );
141 menuBar()->insertItem( i18n("Catalog"), file );
142 // Tools menu actions
143 file = new KPopupMenu( this );
144 TagEditAction->plug( file );
145 menuBar()->insertItem( i18n("Tools"), file );
146 // Player controls menu actions
147 file = new KPopupMenu( this );
148 playerPreviousAction->plug( file );
149 playerPlayAction->plug( file );
150 playerPauseAction->plug( file );
151 playerStopAction->plug( file );
152 playerNextAction->plug( file );
153 menuBar()->insertItem( i18n("Player controls"), file );
154 // Settings menu actions
155 file = new KPopupMenu( this );
156 programSettingsAction->plug( file );
157 // listTableToggleTitleAction->plug( file );
158 menuBar()->insertItem( i18n("Settings"), file );
159 // Help menu actions
160 file = helpMenu(); menuBar()->insertItem( i18n("Help"), file );
162 // Toolbar menu
163 KToolBar * fileToolsToolbar = new KToolBar( this, "File operations" );
164 fileToolsToolbar->setOrientation( Qt::Horizontal );
165 fileToolsToolbar->setIconText( KToolBar::IconOnly );
166 fileToolsToolbar->setLabel( i18n("File operations") );
168 fileNewAction->plug( fileToolsToolbar );
169 fileOpenAction->plug( fileToolsToolbar );
170 fileSaveAction->plug( fileToolsToolbar );
171 fileToolsToolbar->insertSeparator();
172 fileQuitAction->plug( fileToolsToolbar );
174 KToolBar* playerActionsToolbar = new KToolBar( this, "Player controls" );
175 playerActionsToolbar->setOrientation( Qt::Horizontal );
176 playerActionsToolbar->setIconText( KToolBar::TextOnly );
177 playerActionsToolbar->setLabel( i18n("Player controls") );
178 playerPreviousAction->plug( playerActionsToolbar );
179 playerPlayAction->plug( playerActionsToolbar );
180 playerPauseAction->plug( playerActionsToolbar );
181 playerStopAction->plug( playerActionsToolbar );
182 playerNextAction->plug( playerActionsToolbar );
184 moveDockWindow( fileToolsToolbar, Top );
185 moveDockWindow( playerActionsToolbar, Top );
187 // popup menus
188 CTree_PopupMenu = new QPopupMenu( this, "catalog tree popup" );
189 fileNewAction->plug( CTree_PopupMenu );
190 catalogAddNewFolderAction->plug( CTree_PopupMenu );
191 CTree_PopupMenu->insertSeparator(2);
192 playerPlayDirAction->plug( CTree_PopupMenu );
193 playerPlayDirSubdirsAction->plug( CTree_PopupMenu );
194 CTree_PopupMenu->insertSeparator(5);
195 playerEnqueueDirAction->plug( CTree_PopupMenu );
196 playerEnqueueDirSubdirsAction->plug( CTree_PopupMenu );
197 CTree_PopupMenu->insertSeparator(8);
199 CList_PopupMenu = new QPopupMenu( this, "files list popup" );
200 playerPlaySelectionAction->plug( CList_PopupMenu );
201 playerPlaySelectionAction->setEnabled( FALSE );
202 playerEnqueueAction->plug( CList_PopupMenu );
203 playerEnqueueAction->setEnabled( FALSE );
204 CList_PopupMenu->insertSeparator(2);
205 TagEditAction->plug( CList_PopupMenu );
206 TagEditAction->setEnabled( FALSE );
208 CSearchList_PopupMenu = new QPopupMenu( this, "search list popup" );
209 playerPlaySelectionAction->plug( CSearchList_PopupMenu );
210 playerEnqueueAction->plug( CSearchList_PopupMenu );
211 CSearchList_PopupMenu->insertSeparator(2);
212 TagEditAction->plug( CSearchList_PopupMenu );
213 listTableLocateAction->plug( CSearchList_PopupMenu );
215 if( d->dbg() & KMK_DBG_SIGNALS ) kdDebug() << "constructor: connecting signals to slots..." << endl;
217 connect( treeListView, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int ) ),
218 this, SLOT( slotTreeListViewPopupMenuRequested( QListViewItem*, const QPoint &, int ) ) );
219 connect( filesListView, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int ) ),
220 this, SLOT( slotFileListTablePopupMenuRequested( QListViewItem*, const QPoint &, int ) ) );
221 connect( searchFilesListView, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int ) ),
222 this, SLOT( slotSearchListTablePopupMenuRequested( QListViewItem*, const QPoint &, int ) ) );
223 connect( treeListView, SIGNAL( currentChanged( QListViewItem* ) ),
224 this, SLOT( slotTreeListViewCurrentChanged( QListViewItem* ) ) );
225 connect( leSearchFor, SIGNAL( returnPressed() ),
226 this, SLOT( slotSearchButtonClicked() ) );
227 connect( pbSearch, SIGNAL( clicked() ),
228 this, SLOT( slotSearchButtonClicked() ) );
229 connect( pbClear, SIGNAL( clicked() ),
230 this, SLOT( slotClearButtonClicked() ) );
231 connect( pbDone, SIGNAL( clicked() ),
232 this, SLOT( slotDoneButtonClicked() ) );
234 if( d->dbg() & KMK_DBG_SIGNALS ) kdDebug() << "constructor: done!" << endl;
236 FileSaveCalledFromFileSaveAs = FALSE;
238 QDir tmp_dir; tmp_dir.mkdir( "/tmp/kmk", TRUE );
240 setAutoSaveSettings();
241 applyMainWindowSettings( kapp->config(), QString::fromLatin1("MainWindow") );
243 clearCatalogData();
244 if ( d->loadLast() )
246 if ( !d->lastCatalogUsed().isEmpty() )
248 show();
249 loadCatalog( d->lastCatalogUsed() );
254 void kmk::init_interface()
256 main_container_widget = new QWidget( this, "main_container_widget" );
258 setCentralWidget( main_container_widget );
260 kmkwidgetbaseLayout = new QVBoxLayout( main_container_widget, 11, 6, "kmkwidgetbaseLayout");
262 tabWidget1 = new QTabWidget( main_container_widget, "level1_tabwidget" );
264 tab = new QWidget( tabWidget1, "level1_tab1" );
265 tabLayout = new QVBoxLayout( tab, 11, 6, "tab1_Layout");
267 splitter1 = new QSplitter( tab, "splitter1" );
268 splitter1->setMinimumSize( QSize( 182, 60 ) );
269 splitter1->setOrientation( QSplitter::Horizontal );
271 treeListView = new kmk_tree_KListView( splitter1, "treeListView" );
272 treeListView->addColumn( i18n( "Catalog tree " ) );
273 treeListView->setResizePolicy( KListView::AutoOneFit );
274 treeListView->setShowSortIndicator( TRUE );
275 treeListView->setRootIsDecorated( TRUE );
276 treeListView->setItemsMovable( TRUE );
277 treeListView->setDragEnabled( TRUE );
278 treeListView->setDropVisualizer( TRUE );
280 filesListView = new kmk_files_KListView( splitter1, "filesListView" );
281 filesListView->addColumn( i18n( "Folder" ) );
282 filesListView->addColumn( i18n( "Filename" ) );
283 filesListView->addColumn( i18n( "Artist" ) );
284 filesListView->addColumn( i18n( "Title" ) );
285 filesListView->addColumn( i18n( "Album" ) );
286 filesListView->addColumn( i18n( "Genre" ) );
287 filesListView->addColumn( i18n( "Comment" ) );
288 filesListView->addColumn( i18n( "Year" ) );
289 filesListView->addColumn( i18n( "Track #" ) );
290 filesListView->addColumn( i18n( "Duration" ) );
291 filesListView->addColumn( i18n( "Size" ) );
292 filesListView->addColumn( i18n( "Bitrate" ) );
293 filesListView->addColumn( i18n( "Sampling rate" ) );
294 filesListView->addColumn( i18n( "Channels" ) );
295 filesListView->addColumn( i18n( "Type" ) );
296 filesListView->addColumn( i18n( "READ ONLY" ) );
297 filesListView->setResizePolicy( KListView::AutoOneFit );
298 filesListView->setAllColumnsShowFocus( TRUE );
299 filesListView->setShowSortIndicator( TRUE );
300 filesListView->setRootIsDecorated( FALSE );
301 filesListView->setItemsMovable( FALSE );
302 filesListView->setSelectionMode( QListView::Extended );
303 filesListView->setDragEnabled( TRUE );
304 filesListView->setDropVisualizer( TRUE );
305 tabLayout->addWidget( splitter1 );
306 tabWidget1->insertTab( tab, i18n("Catalog") );
308 tab_2 = new QWidget( tabWidget1, "level1_tab2" );
309 tabLayout_2 = new QVBoxLayout( tab_2, 11, 6, "tab2_Layout");
311 layout5 = new QVBoxLayout( 0, 0, 6, "layout5");
313 tabWidget2 = new QTabWidget( tab_2, "level2_tabwidget" );
314 tabWidget2->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)0, (QSizePolicy::SizeType)0, 0, 0,
315 tabWidget2->sizePolicy().hasHeightForWidth() ) );
316 tabWidget2->setMinimumSize( QSize( 480, 158 ) );
318 tab_3 = new QWidget( tabWidget2, "level2_tab1" );
320 pbSearch = new QPushButton( tab_3, "pbSearch" );
321 pbSearch->setGeometry( QRect( 370, 10, 100, 26 ) );
322 pbSearch->setText( i18n( "Go!" ) );
323 pbSearch->setDefault( TRUE );
325 pbClear = new QPushButton( tab_3, "pbClear" );
326 pbClear->setGeometry( QRect( 370, 50, 100, 26 ) );
327 pbClear->setText( i18n( "Clear" ) );
329 pbDone = new QPushButton( tab_3, "pbDone" );
330 pbDone->setGeometry( QRect( 370, 90, 100, 26 ) );
331 pbDone->setText( i18n( "Done" ) );
333 frame1 = new QFrame( tab_3, "frame1" );
334 frame1->setGeometry( QRect( 10, 10, 350, 110 ) );
335 frame1->setFrameShape( QFrame::StyledPanel );
336 frame1->setFrameShadow( QFrame::Raised );
338 textLabel1 = new QLabel( frame1, "textLabel1" );
339 textLabel1->setGeometry( QRect( 10, 10, 100, 16 ) );
340 textLabel1->setText( i18n( "Look for" ) );
342 leSearchFor = new KLineEdit( frame1, "leSearchFor" );
343 leSearchFor->setGeometry( QRect( 10, 30, 330, 24 ) );
344 tabWidget2->insertTab( tab_3, i18n("Base") );
346 tab_4 = new QWidget( tabWidget2, "level2_tab2" );
347 tabWidget2->insertTab( tab_4, i18n("Advanced") );
348 layout5->addWidget( tabWidget2 );
350 searchFilesListView = new kmk_files_KListView( tab_2, "searchFilesListView" );
351 searchFilesListView->addColumn( i18n( "Folder" ) );
352 searchFilesListView->addColumn( i18n( "Filename" ) );
353 searchFilesListView->addColumn( i18n( "Artist" ) );
354 searchFilesListView->addColumn( i18n( "Title" ) );
355 searchFilesListView->addColumn( i18n( "Album" ) );
356 searchFilesListView->addColumn( i18n( "Genre" ) );
357 searchFilesListView->addColumn( i18n( "Comment" ) );
358 searchFilesListView->addColumn( i18n( "Year" ) );
359 searchFilesListView->addColumn( i18n( "Track #" ) );
360 searchFilesListView->addColumn( i18n( "Duration" ) );
361 searchFilesListView->addColumn( i18n( "Size" ) );
362 searchFilesListView->addColumn( i18n( "Bitrate" ) );
363 searchFilesListView->addColumn( i18n( "Sampling rate" ) );
364 searchFilesListView->addColumn( i18n( "Channels" ) );
365 searchFilesListView->addColumn( i18n( "Type" ) );
366 searchFilesListView->addColumn( i18n( "READ ONLY" ) );
367 searchFilesListView->setResizePolicy( KListView::AutoOneFit );
368 searchFilesListView->setAllColumnsShowFocus( TRUE );
369 searchFilesListView->setShowSortIndicator( TRUE );
370 searchFilesListView->setRootIsDecorated( FALSE );
371 searchFilesListView->setItemsMovable( FALSE );
372 searchFilesListView->setDragEnabled( TRUE );
373 searchFilesListView->setDropVisualizer( TRUE );
374 searchFilesListView->setSelectionMode( QListView::Extended );
375 layout5->addWidget( searchFilesListView );
376 tabLayout_2->addLayout( layout5 );
377 tabWidget1->insertTab( tab_2, i18n("Search") );
378 kmkwidgetbaseLayout->addWidget( tabWidget1 );
379 // resize( QSize(742, 507).expandedTo(minimumSizeHint()) );
380 // clearWState( WState_Polished );
382 // tab order
383 setTabOrder( tabWidget1, treeListView );
384 setTabOrder( treeListView, filesListView );
385 setTabOrder( filesListView, tabWidget2 );
386 setTabOrder( tabWidget2, leSearchFor );
387 setTabOrder( leSearchFor, pbSearch );
388 setTabOrder( pbSearch, pbClear );
389 setTabOrder( pbClear, pbDone );
390 setTabOrder( pbDone, searchFilesListView );
393 kmk::~kmk()
395 saveMainWindowSettings( kapp->config(), QString::fromLatin1("MainWindow") );
396 Q_CHECK_PTR( d );
397 switch ( CatalogState )
399 case NoCatalog:
400 d->setLastCatalogUsed( "" );
401 break;
402 case Modified:
403 case Saved:
404 d->setLastCatalogUsed( catalogFileName );
405 break;
406 default:
407 break;
409 d->saveSettings( kapp->config() );
411 kapp->config()->setGroup( "GUI" );
412 kapp->config()->writeEntry( "splitter config", splitter1->sizes() );
413 kapp->config()->writeEntry( "main win position", pos() );
415 clearCatalogData();
416 if (kmkProgress) delete ( kmkProgress );
417 if (kmkProgressTimer) delete ( kmkProgressTimer );
418 if (kmkSmooth) delete ( kmkSmooth );
419 if ( d ) delete ( d );
423 void kmk::slotUpdateProgressDisp()
425 if ( files_to_read )
426 kmkProgress->kPrg->setProgress( total_files );
427 /* qDebug( " setting progres to "+QString::number( (int)((total_bytes/(float)bytes_to_read)*100 )) );
428 qDebug( " need to read: "+QString::number( bytes_to_read ) );
429 qDebug( " read: "+QString::number( total_bytes ) );*/
432 void kmk::slotFileNew()
434 if( CatalogState == Modified )
435 switch( KMessageBox::questionYesNoCancel( this,
436 i18n("The catalog [%1] contains unsaved changes.\n"
437 "Do you want to save the changes?")
438 .arg(catalogFileName),
439 i18n("KDE Music Kataloger") ) ) {
440 case KMessageBox::Yes: // Save
441 slotFileSave();
442 break;
443 case KMessageBox::No: // Discard - just continue
444 break;
445 case KMessageBox::Cancel: // Cancel creating new catalog
446 return;
447 break;
449 QFileDialog* fd = new QFileDialog( this, "file dialog", TRUE );
450 fd->setCaption( i18n("Select directory to scan") );
451 fd->setMode( QFileDialog::Directory );
452 fd->setFilter( "*" );
453 /* KFileDialog * t = new KFileDialog( ":&lt;scandir&gt;", "*", this, i18n("Select directory to scan"), TRUE );
454 t->exec(); */
455 // = KFileDialog::getExistingDirectory( ":&lt;scandir&gt;", this, i18n("Select directory to scan") );
456 if ( fd->exec() == QDialog::Accepted ) {
457 // reset counters;
458 clearCatalogData();
459 subDlevel = 0;
460 QString dirName = fd->selectedFile();
461 // determine bytes to read;
462 kmkSmooth->start();
463 kmkProgress->kPrg->setTotalSteps(0);
464 kmkProgress->kPrg->setProgress(0);
465 kmkProgress->show();
466 QTime t; t.start();
467 break_long_disk_operation = FALSE;
468 bytes_to_read = 0; files_to_read = 0;
469 bytes_to_read_by_traverse( dirName );
470 if ( files_to_read )
472 kdDebug() << "Time elapsed: " << t.elapsed() << " ms, need to read: " << (bytes_to_read/(1024*1024))
473 << " MB, files to read: " << files_to_read << endl;
474 t.restart();
475 kmkProgress->kPrg->setTotalSteps( files_to_read );
476 // arm timer event; 300 ms interval - can be done in constructor
477 connect( kmkProgressTimer, SIGNAL( timeout() ), this, SLOT( slotUpdateProgressDisp() ) );
478 kmkProgressTimer->start(_KMK_UPDATE_PERIOD,FALSE);
479 // show widget, displaying load progress
480 // start timer
481 traverse_tree( dirName ); // should update total_bytes as it scans
482 setCatalogStateAndUpdate( Modified );
483 average_file_size = ( total_bytes/total_files );
484 kdDebug() << "Total time: " << t.elapsed() << " ms, read: " << (total_bytes/(1024*1024))
485 << " MB, files read: " << total_files << endl;
486 // kdDebug() << "(slotFileNew()):STATS: total_files = " << total_files << ";" << endl;
487 // kdDebug() << "(slotFileNew()):STATS: total_folders = " << total_folders << ";" << endl;
488 // kdDebug() << "(slotFileNew()):STATS: total_bytes = " << total_bytes << " B;" << endl;
489 // kdDebug() << "(slotFileNew()):STATS: total_playtime = " << total_play_time << " sec;" << endl;
490 // kdDebug() << "(slotFileNew()):STATS: average_file_size = " << average_file_size << " B;" << endl;
491 kmkProgress->hide();
492 // hide widget
493 // stop timer
494 // Enable actions wich work only with data...
495 slotTreeListViewCurrentChanged( (QListViewItem*) treeListView->firstChild() );
496 QTimer::singleShot( _KMK_UPDATE_PERIOD, this, SLOT( slotFileSaveAs() ) );
498 else
500 setCatalogStateAndUpdate( NoCatalog );
501 clearCatalogData();
502 subDlevel = 0;
503 KMessageBox::sorry( this, i18n("There are no files of supported types in [%1].").arg(dirName),
504 i18n("KDE Music Kataloger") );
507 delete( fd );
510 void kmk::slotFileOpen()
512 if( CatalogState == Modified )
513 switch( KMessageBox::questionYesNoCancel( this,
514 i18n("The catalog [%1] contains unsaved changes.\n"
515 "Do you want to save the changes?")
516 .arg(catalogFileName),
517 i18n("KDE Music Kataloger") ) ) {
518 case KMessageBox::Yes: // Save
519 slotFileSave();
520 break;
521 case KMessageBox::No: // Discard - just continue
522 break;
523 case KMessageBox::Cancel: // Cancel creating new catalog
524 return;
525 break;
527 QFileDialog* fd = new QFileDialog( this, "file dialog", TRUE );
528 fd->setCaption( i18n("Select catalog file to load") );
529 fd->setMode( QFileDialog::ExistingFile );
530 fd->setFilter( i18n("All KMK catalog files %1").arg("(*.kmk)") );
531 if ( fd->exec() != QDialog::Accepted ) delete fd;
532 else
534 QString fileName = fd->selectedFile(); delete fd;
535 qApp->processEvents();
536 loadCatalog( fileName );
537 Q_CHECK_PTR( d );
538 d->setLastCatalogUsed( fileName );
539 d->saveSettings( kapp->config() );
543 void kmk::slotCatalogAddNewFolder()
545 QFileDialog* fd = new QFileDialog( this, "file dialog", TRUE );
546 fd->setCaption( i18n("Select directory to scan") );
547 fd->setMode( QFileDialog::Directory );
548 fd->setFilter( "*" );
549 /* KFileDialog * t = new KFileDialog( ":&lt;scandir&gt;", "*", this, i18n("Select directory to scan"), TRUE );
550 t->exec(); */
551 // = KFileDialog::getExistingDirectory( ":&lt;scandir&gt;", this, i18n("Select directory to scan") );
552 if ( fd->exec() == QDialog::Accepted )
554 subDlevel = 0;
555 QString dirName = fd->selectedFile();
556 if ( catalog_has_dir( dirName ) )
558 KMessageBox::sorry( this, i18n("Sorry, but the folder [%1]\nis already present in this catalog!")
559 .arg(dirName),i18n("KDE Music Kataloger") );
560 delete fd;
561 return;
563 // clearCatalogData();
564 // reset counters;
565 Q_ULLONG bc01 = total_bytes; Q_ULLONG bc02 = total_files;
566 Q_ULLONG bc03 = total_folders; Q_ULLONG bc04 = total_play_time;
567 bytes_to_read = 0; files_to_read = 0;
568 total_bytes = 0; total_files = 0;
569 total_folders = 0; total_play_time = 0;
570 // determine bytes to read;
571 kmkSmooth->start();
572 kmkProgress->kPrg->setProgress(0);
573 kmkProgress->show();
574 // QTime t; t.start();
575 break_long_disk_operation = FALSE;
576 bytes_to_read_by_traverse( dirName );
577 // kdDebug() << "Time elapsed: " << t.elapsed() << " ms, need to read: " << (bytes_to_read/(1024*1024))
578 // << " MB, files to read: " << files_to_read << endl;
579 // t.restart();
580 // arm timer event
581 if ( files_to_read )
583 connect( kmkProgressTimer, SIGNAL( timeout() ), this, SLOT( slotUpdateProgressDisp() ) );
584 kmkProgressTimer->start(_KMK_UPDATE_PERIOD,FALSE);
585 // show widget, displaying load progress
586 // start timer
587 traverse_tree( dirName ); // should update total_bytes as it scans
588 setCatalogStateAndUpdate( Modified );
589 // kdDebug() << "Total time: " << t.elapsed() << " ms, read: " << (total_bytes/(1024*1024))
590 // << " MB, files read: " << total_files << endl;
591 total_bytes += bc01; total_files += bc02;
592 total_folders += bc03; total_play_time += bc04;
593 if ( total_files ) average_file_size = ( total_bytes/total_files );
594 // kdDebug() << "(slotCatalogAddNewFolder()):STATS: total_files = " << total_files << ";" << endl;
595 // kdDebug() << "(slotCatalogAddNewFolder()):STATS: total_folders = " << total_folders << ";" << endl;
596 // kdDebug() << "(slotCatalogAddNewFolder()):STATS: total_bytes = " << total_bytes << " B;" << endl;
597 // kdDebug() << "(slotCatalogAddNewFolder()):STATS: total_playtime = " << total_play_time << " sec;" << endl;
598 // kdDebug() << "(slotCatalogAddNewFolder()):STATS: average_file_size = " << average_file_size << " B;" << endl;
599 // hide widget
600 kmkProgress->hide();
601 // stop timer
602 // QTimer::singleShot( _KMK_UPDATE_PERIOD, this, SLOT( slotFileSaveAs() ) );
604 else KMessageBox::sorry( this, i18n("There are no files of supported types in [%1].").arg(dirName),
605 i18n("KDE Music Kataloger") );
607 delete( fd );
610 /** Saves the catalog in memory to the file @p catalogFileName;
611 * If the string, being a filename in @p catalogFileName does not have
612 * a .kmk extension - this slot adds it
614 void kmk::slotFileSave()
616 if( CatalogState != Modified ) return;
617 /* We need to check whether catalogFileName exists, and what permissions it has.
618 If there is a problem - inform the user. */
619 bool fileOK=TRUE;
620 if( catalogFileName.isEmpty() || catalogFileName.isNull() )
621 catalogFileName = QDir::homeDirPath()+"/"+_KMK_NEWCATALOG;
622 QFileInfo * _cf = new QFileInfo( catalogFileName );
623 /* Make sure .kmk is appended to the file name...*/
624 if (_cf->extension(FALSE).lower().compare("kmk") != 0)
625 { catalogFileName.append(".kmk"); _cf->setFile( catalogFileName ); }
626 if( (_cf->exists())&&(!_cf->isWritable()) ){ /* File is NOT OK, existing, but not writable. Inform. */
627 fileOK = FALSE;
628 KMessageBox::sorry( this, i18n("Could not open %1 for writing!").arg(catalogFileName),
629 i18n("KDE Music Kataloger") );
631 if( (_cf->exists())&&(!_cf->isFile()) ){ /* File is NOT OK, it is not even a file. Inform. */
632 fileOK = FALSE;
633 KMessageBox::sorry( this, i18n("The filesystem item %1 exists, but it is not a file!").arg(catalogFileName),
634 i18n("KDE Music Kataloger") );
636 if( (FileSaveCalledFromFileSaveAs) && (_cf->exists())
637 && (_cf->isWritable()) ) /* File is OK, but we need to overwrite it... ask what to do. */
639 if( KMessageBox::questionYesNo( this,
640 i18n("A file called [%1] already exists.\nDo you want to overwrite it?").arg(catalogFileName),
641 i18n("KDE Music Kataloger") ) != KMessageBox::Yes ) fileOK = FALSE;
643 FileSaveCalledFromFileSaveAs = FALSE;
644 if( fileOK ) /* After the checks above are passed - lets write! */
646 QDomDocument doc( "KMK_catalog_file" ); QDomElement e = doc.createElement( "KMK_catalog_file_data" );
647 e.setAttribute( "Version", 1 ); doc.appendChild( e );
648 QDomElement e2 = doc.createElement( "CatalogFile_Statistics" ); e.appendChild( e2 );
649 e2.setAttribute( "Total_files_count", (double) total_files );
650 e2.setAttribute( "Total_bytes_count", (double) total_bytes );
651 e2.setAttribute( "Total_dirs_count", (double) total_folders );
652 e2.setAttribute( "Total_secs_count", (double) total_play_time );
653 e2.setAttribute( "Average_file_size", (double) average_file_size );
654 e2 = doc.createElement( "CatalogFile_TreeDescription" ); e.appendChild( e2 );
656 generateListViewXML( treeListView, doc, e2 );
658 QDomElement tag;
659 e2 = doc.createElement( "CatalogFile_Objects" ); e.appendChild( e2 );
660 e2.setAttribute( "Total_MObjs_count", (double) MusicCatalog.count() );
662 for( MmDataList::iterator it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
664 /* ask our DOM Document object to create a new DomElement, and fill this
665 new element with appropriate data */
666 tag = doc.createElement( "MObj" );
667 tag.setAttribute( "Folder", (*it).Folder().utf8() );
668 tag.setAttribute( "Filename", (*it).FileName().utf8() );
669 tag.setAttribute( "Artist", (*it).Artist().utf8() );
670 tag.setAttribute( "Title", (*it).Title().utf8() );
671 tag.setAttribute( "Album", (*it).Album().utf8() );
672 tag.setAttribute( "Genre", (*it).Genre().utf8() );
673 tag.setAttribute( "Comment", (*it).Comment().utf8() );
674 tag.setAttribute( "Year", (*it).Year() );
675 tag.setAttribute( "TrackNumber", (*it).TrackNum() );
676 tag.setAttribute( "Length", (*it).Length() );
677 tag.setAttribute( "Modified", (*it).ModifiedTime() );
678 tag.setAttribute( "Size", (*it).FileSize() );
679 tag.setAttribute( "Channels", (*it).Channels() );
680 tag.setAttribute( "BitRate", (*it).BitRate() );
681 tag.setAttribute( "SampleRate", (*it).SampleRate() );
682 tag.setAttribute( "IsReadOnly", ( ((*it).IsReadOnly()) ? "YES" : "NO" ) );
683 tag.setAttribute( "IsFolder", ( ((*it).IsDir()) ? "YES" : "NO" ) );
684 /* Then, ask the DOM Document to add this new element in the correct place... */
685 e2.appendChild( tag );
688 QString xml = doc.toString(2);
689 QFile file( catalogFileName );
690 if ( file.open( IO_WriteOnly ) ) {
691 file.writeBlock( xml, xml.length() );
692 file.flush();
693 file.close();
694 this->setCaption( i18n("Catalog [%1] - ").arg(catalogFileName) + savedCaption );
695 setCatalogStateAndUpdate( Saved );
696 } else QMessageBox::warning( this, i18n("KDE Music Kataloger"), i18n("Could not open %1 for writing!").arg(catalogFileName),
697 QMessageBox::Cancel,QMessageBox::NoButton,QMessageBox::NoButton );
699 else this->setCaption( i18n("Catalog [%1][UNSAVED] - ").arg(catalogFileName) + savedCaption );
702 void kmk::slotFileSaveAs()
704 QFileDialog* fd = new QFileDialog( 0, "file dialog", TRUE );
705 fd->setCaption( i18n("Select file to save catalog to") );
706 fd->setMode( QFileDialog::AnyFile );
707 fd->setFilter( i18n("All KMK catalog files %1").arg("(*.kmk)") );
708 QString fileName;
709 if ( fd->exec() == QDialog::Accepted )
711 catalogFileName = fd->selectedFile();
712 setCatalogStateAndUpdate( Modified );
713 FileSaveCalledFromFileSaveAs = TRUE;
714 QTimer::singleShot( _KMK_UPDATE_PERIOD, this, SLOT( slotFileSave() ) );
716 delete fd;
719 void kmk::slotCatalogFileClose()
721 if( CatalogState == Modified )
722 switch( KMessageBox::questionYesNoCancel( this,
723 i18n("The catalog [%1] contains unsaved changes.\n"
724 "Do you want to save the changes?")
725 .arg(catalogFileName),
726 i18n("KDE Music Kataloger") ) ) {
727 case KMessageBox::Yes: // Save
728 slotFileSave();
729 break;
730 case KMessageBox::No: // Discard - just continue
731 break;
732 case KMessageBox::Cancel: // Cancel opreation
733 return;
734 break;
736 // Ensure there is no data laying around...
737 clearCatalogData();
740 void kmk::slotCatalogFileStats()
742 QString tmp = formatted_string_from_seconds( total_play_time );
743 KMessageBox::information( 0,
744 i18n("<h3>Catalog statistics</h3>"
745 "<h4>This catalog represents:</h4>"
746 "<pre>Total capacity : %1 MB<br/>"
747 "Total files : %2<br/>"
748 "Total folders : %3<br/>"
749 "Total playtime : %4<br/>"
750 "Average filesize : %5 MB</pre>")
751 .arg(total_bytes/(float)(1024*1024),-8,'f',1).arg(total_files,-8).arg(total_folders,-8)
752 .arg(tmp).arg(average_file_size/(float)(1024*1024),-8,'f',3),
753 i18n("KDE Music Kataloger"));
756 void kmk::slotFileQuit()
758 close();
761 void kmk::closeEvent( QCloseEvent* ce )
763 if( !ce )
765 kdDebug() << " Kkmk::closeEvent: bad object " << endl;
766 return;
768 if( CatalogState == Modified )
769 switch( KMessageBox::questionYesNoCancel( this,
770 i18n("The catalog [%1] contains unsaved changes.\n"
771 "Do you want to save the changes before exiting?")
772 .arg(catalogFileName),
773 i18n("KDE Music Kataloger") ) ) {
774 case KMessageBox::Yes: // Save & Exit
775 slotFileSave();
776 ce->accept();
777 break;
778 case KMessageBox::No: // Discard - just Exit
779 ce->accept();
780 break;
781 case KMessageBox::Cancel: // Cancel - no nothing
782 ce->ignore();
783 break;
785 else ce->accept();
788 void kmk::slotProgramSettings()
790 // WARNING: kmkSettingsDialog has Qt::WDestructiveClose flag set, it WILL destroy itself
791 // also, it internally calls exec() - so we only create it, passing relevant data
792 // to its contructor
793 uint * k = new uint;
794 new kmkSettingsDialog( k );
797 void kmk::slotTagEdit()
799 MmDataPList *files = new MmDataPList; if( !files ) return;
800 MmData *tmp = 0; // zero-init our MmData pointer
801 KListViewItem *node = 0;
802 if( _popup_at_search )
803 node = (KListViewItem*) searchFilesListView->firstChild();
804 else
805 node = (KListViewItem*) filesListView->firstChild();
807 while ( node )
809 if ( node->isSelected() )
811 tmp = findMmData( node->text( 0 ), node->text( 1 ) );
812 if ( tmp ) if ( (*tmp).IsDir()==FALSE ) files->append( tmp );
814 node = (KListViewItem*) node->nextSibling();
817 if ( files->isEmpty() ) { delete files; return; }
819 /** kmkTagEdit's constructor takes care of everything for us -
820 * we don't even need to call show() or exec() - it is done automagically;
822 uint k = 0;
823 new kmkTagEdit( files, &k );
824 // kdDebug() << "(kmk::slotTagEdit) TagEditDialog returns: " << k << endl;
825 if( k ) {
826 setCatalogStateAndUpdate( Modified );
827 slotTreeListViewCurrentChanged( treeListView->currentItem() );
829 delete files;
832 void kmk::slotListTableToggleTitle()
834 // QTable* tbl = ((kmkWidget*) kmk_widg )->FileList();
835 // tbl->hideColumn(1);
838 void kmk::slotPlayerPrevious()
840 QProcess* ext_prog = new QProcess( this, "player_ext");
841 ext_prog->clearArguments();
842 ext_prog->addArgument( player_bin );
843 // action parameter
844 ext_prog->addArgument( "-r" );
845 // ext_prog->addArgument( "/usr/sbin/xmms" );
846 ext_prog->launch( "" );
847 delete ext_prog;
850 void kmk::slotPlayerPlay()
852 QProcess* ext_prog = new QProcess( this, "player_ext");
853 ext_prog->clearArguments();
854 ext_prog->addArgument( player_bin );
855 // action parameter
856 ext_prog->addArgument( "-p" );
857 // ext_prog->addArgument( "/usr/sbin/xmms" );
858 if( !ext_prog->start() )
860 kdDebug() << "Cannot start: " << player_bin << " " << endl;
862 delete ext_prog;
865 void kmk::slotPlayerPause()
867 QProcess* ext_prog = new QProcess( this, "player_ext");
868 ext_prog->clearArguments();
869 ext_prog->addArgument( player_bin );
870 // action parameter
871 ext_prog->addArgument( "-u" );
872 // ext_prog->addArgument( "/usr/sbin/xmms" );
873 ext_prog->launch( "" );
874 delete ext_prog;
877 void kmk::slotPlayerStop()
879 QProcess* ext_prog = new QProcess( this, "player_ext");
880 ext_prog->clearArguments();
881 ext_prog->addArgument( player_bin );
882 // action parameter
883 ext_prog->addArgument( "-s" );
884 // ext_prog->addArgument( "/usr/sbin/xmms" );
885 ext_prog->launch( "" );
886 delete ext_prog;
889 void kmk::slotPlayerNext()
891 QProcess* ext_prog = new QProcess( this, "player_ext");
892 ext_prog->clearArguments();
893 ext_prog->addArgument( player_bin );
894 // action parameter
895 ext_prog->addArgument( "-f" );
896 // ext_prog->addArgument( "/usr/sbin/xmms" );
897 ext_prog->launch( "" );
898 delete ext_prog;
902 * In this member we use the fact, that when QTable's selectionMode() is MultiRow,
903 * meaning that whole rows selection is only allowed, QTable passes these
904 * rows as seperate selections - 2 successive rows will give us 2 selections
905 * with each selections topRow() and bottomRow() showing the number of the selected
906 * row in the table ---THIS ALL IS FALSE
908 void kmk::slotPlayerPlaySelection()
910 MmDataPList *files = new MmDataPList;
911 MmData *tmp = 0; // zero-init our MmData pointer
912 KListViewItem *node = 0;
913 if( _popup_at_search)
914 node = (KListViewItem*) searchFilesListView->firstChild();
915 else
916 node = (KListViewItem*) filesListView->firstChild();
918 while ( node )
920 if ( node->isSelected() )
922 tmp = findMmData( node->text( 0 ), node->text( 1 ) );
923 if ( tmp ) if ( (*tmp).IsDir()==FALSE ) files->append( tmp );
925 node = (KListViewItem*) node->nextSibling();
928 QString flNm = "/tmp/kmk/kmk_lst_"+QString::number( random() )+".m3u";
929 QFile file( flNm );
930 if ( file.open( IO_WriteOnly ) )
932 QTextStream stream( &file );
933 stream << "#EXTM3U" << endl;
934 for ( tmp = files->first(); tmp; tmp = files->next() )
936 stream << "#EXTINF:" << tmp->Length() << ", " << tmp->Artist() << " - " << tmp->Title() << endl;
937 stream << tmp->Folder() << "/" << tmp->FileName() << endl;
939 file.close();
941 QProcess* ext_prog = new QProcess( this, "player_ext");
942 ext_prog->clearArguments();
943 ext_prog->addArgument( player_bin );
944 // action parameter
945 ext_prog->addArgument( "-p" );
946 ext_prog->addArgument( flNm );
947 ext_prog->launch( "" );
948 delete ext_prog;
949 // file.remove();
951 else QMessageBox::warning( this, i18n("KDE Music Kataloger"),i18n("Could not open %1 for writing!").arg(flNm),
952 QMessageBox::Cancel,QMessageBox::NoButton,QMessageBox::NoButton );
953 delete files;
956 void kmk::slotPlayerEnqueue()
958 MmDataPList *files = new MmDataPList;
959 MmData *tmp = 0; // zero-init our MmData pointer
960 KListViewItem *node = 0;
961 if( _popup_at_search)
962 node = (KListViewItem*) searchFilesListView->firstChild();
963 else
964 node = (KListViewItem*) filesListView->firstChild();
966 while ( node )
968 if ( node->isSelected() )
970 tmp = findMmData( node->text( 0 ), node->text( 1 ) );
971 if ( tmp ) if ( (*tmp).IsDir()==FALSE ) files->append( tmp );
973 node = (KListViewItem*) node->nextSibling();
976 QString flNm = "/tmp/kmk/kmk_lst_"+QString::number( random() )+".m3u";
977 QFile file( flNm );
978 if ( file.open( IO_WriteOnly ) )
980 QTextStream stream( &file );
981 stream << "#EXTM3U" << endl;
982 for ( tmp = files->first(); tmp; tmp = files->next() )
984 stream << "#EXTINF:" << tmp->Length() << ", " << tmp->Artist() << " - " << tmp->Title() << endl;
985 stream << tmp->Folder() << "/" << tmp->FileName() << endl;
987 file.close();
989 QProcess* ext_prog = new QProcess( this, "player_ext");
990 ext_prog->clearArguments();
991 ext_prog->addArgument( player_bin );
992 // action parameter
993 ext_prog->addArgument( "-e" );
994 ext_prog->addArgument( flNm );
995 ext_prog->launch( "" );
996 delete ext_prog;
998 else QMessageBox::warning( this, i18n("KDE Music Kataloger"),i18n("Could not open %1 for writing!").arg(flNm),
999 QMessageBox::Cancel,QMessageBox::NoButton,QMessageBox::NoButton );
1000 delete files;
1004 void kmk::slotPlayerEnqueueDir()
1006 _kmk_include_subdirs = FALSE; //tell playerDir to NOT include subfolder(s) contents
1007 playerDir( "-e" ); // here we enqueue
1010 void kmk::slotPlayerEnqueueDirSubdirs()
1012 _kmk_include_subdirs = TRUE; //tell playerDir to include subfolder(s) contents
1013 playerDir( "-e" ); // here we enqueue
1016 void kmk::slotPlayerPlayDir()
1018 _kmk_include_subdirs = FALSE; //tell playerDir to NOT include subfolder(s) contents
1019 playerDir( "-p" ); // and here we play
1022 void kmk::slotPlayerPlayDirSubdirs()
1024 _kmk_include_subdirs = TRUE; //tell playerDir to include subfolder(s) contents
1025 playerDir( "-p" ); // and here we play
1028 void kmk::slotTreeListViewPopupMenuRequested( QListViewItem* itm, const QPoint &pos, int col )
1030 /* Take care of annoying warnings ..... */
1031 Q_UNUSED(itm); Q_UNUSED(col);
1032 CTree_PopupMenu->popup( pos );
1035 void kmk::slotTreeListViewCurrentChanged( QListViewItem * itm )
1037 if ( MusicCatalog.isEmpty() ) { kdDebug() << "LVupdate: called with empty catalog" << endl; return; }
1038 // TagEditAction->setEnabled( FALSE );
1039 QListViewItem* lv = itm; QString p;
1040 // kdDebug() << "treeListView dropHighlighter() says: " << treeListView->dropHighlighter() << endl;
1042 // QTime ptime; ptime.start();
1044 while ( TRUE ) {
1045 p.prepend( lv->text(0) );
1046 if( lv->parent() ) { if( lv->parent()->text(0).compare("/") != 0 ) p.prepend( "/" ); lv = lv->parent(); }
1047 else break;
1049 // kdDebug() << "LVupdate: user clicked: " << p << endl;
1050 // kdDebug() << "(kmk):LVupdate: determine hole selected folder name time: " << ptime.elapsed() << " ms." << endl;
1051 // ptime.restart();
1053 MmDataList::iterator it;
1054 bool found = FALSE;
1055 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1056 if( ((*it).IsDir()) && ( (*it).Folder().compare(p)==0 ) ){ found = TRUE; break; }
1057 int folder_files = (found) ? (*it).FileSize() : 0;
1059 // kdDebug() << "(kmk):LVupdate: locate folder as *MmData time: " << ptime.elapsed() << " ms. ff=" << folder_files << endl;
1060 // ptime.restart();
1062 // disble list for manipulation : flicker care
1063 filesListView->setEnabled( FALSE );
1064 while( folder_files > filesListView->childCount() )
1065 new KListViewItem( (QListView*) filesListView, 0 );
1066 while( folder_files < filesListView->childCount() ) {
1067 KListViewItem* t = (KListViewItem*) filesListView->lastItem();
1068 if ( t ) delete t;
1070 // kdDebug() << "(kmk):LVupdate: table setup time: " << ptime.elapsed() << " ms." << endl;
1071 // ptime.restart();
1073 if ( found ) {
1074 it++;
1075 while ( (( (*it).Folder().compare(p)!=0 )) ) it++;
1077 QDateTime dt;
1078 KListViewItem * sel_item = 0;
1079 KListViewItem * new_item = (KListViewItem*) filesListView->firstChild();
1080 for ( int k=0; k<folder_files; it++,k++ )
1082 if ( !new_item ) qFatal("Something horrible happened!");
1083 if ( locateMode )
1084 if ( ( (*it).Folder().compare( DelSelDir ) == 0 ) && ( (*it).FileName().compare( DelSelFile ) == 0 ) )
1085 { sel_item = new_item; locateMode = FALSE; }
1086 new_item->setText( 0, (*it).Folder() ); // folder - for use in play or enqueue file(s) action
1087 new_item->setText( 1, (*it).FileName() ); // file
1088 new_item->setText( 2, (*it).Artist() ); // artist
1089 new_item->setText( 3, (*it).Title() ); // title
1090 new_item->setText( 4, (*it).Album() ); // album
1091 new_item->setText( 5, (*it).Genre() ); // genre
1092 new_item->setText( 6, (*it).Comment() ); // comment
1093 new_item->setText( 7, ((*it).Year()!=0)?QString::number( (*it).Year() ):"" ); // song year
1094 new_item->setText( 8, ((*it).TrackNum()!=0)?QString::number( (*it).TrackNum() ):"" ); // track number
1095 new_item->setText( 9, formatted_string_from_seconds( (*it).Length() ) ); // length
1096 new_item->setText( 10, QString::number( (*it).FileSize() / 1024 ) + " kB" ); // file size
1097 // dt.setTime_t( (*it).ModifiedTime() );
1098 new_item->setText( 11, QString::number( (*it).BitRate()) ); // bitrate
1099 new_item->setText( 12, QString::number( (*it).SampleRate()) ); // samplerate
1100 new_item->setText( 13, QString::number( (*it).Channels()) ); // channels
1101 new_item->setText( 14, "SOME" ); // type
1102 new_item->setText( 15, (*it).IsReadOnly()?"YES":"_no_" ); // read only
1103 new_item = (KListViewItem*) new_item->nextSibling();
1106 // kdDebug() << "(kmk):LVupdate: table fill time: " << ptime.elapsed() << " ms." << endl;
1107 // ptime.restart();
1109 // update table contets after manipulation
1110 filesListView->clearSelection();
1111 // here we update the first 5 columns' width
1112 Q_CHECK_PTR( d );
1113 // honor user setting whether he/she wants auto-column-width, or likes speed more
1114 if ( d->autoColumnWidth() )
1115 for ( ushort k=0; k<7; k++) filesListView->adjustColumn( k );
1116 if ( sel_item ) {
1117 filesListView->ensureItemVisible( sel_item );
1118 filesListView->setSelected( sel_item, TRUE );
1119 DelSelDir = ""; DelSelFile = "";
1121 filesListView->setEnabled( TRUE );
1124 void kmk::slotFileListTablePopupMenuRequested( QListViewItem* itm, const QPoint &pos, int col )
1126 Q_UNUSED(itm); Q_UNUSED(col);
1127 _popup_at_search = FALSE;
1128 CList_PopupMenu->exec( pos );
1131 void kmk::slotSearchListTablePopupMenuRequested( QListViewItem* itm, const QPoint &pos, int col )
1133 Q_UNUSED(itm); Q_UNUSED(col);
1134 _popup_at_search = TRUE;
1135 CSearchList_PopupMenu->exec( pos );
1139 * This function locates the correct place in the catalog tree, selects the
1140 * correct folder and then informs the method updating
1141 * the file list to select AND show the reqested file
1143 void kmk::slotLocateRequested()
1145 // this below is for locating results in the same folder to work,
1146 // without the need to manually change the currently selected folder
1147 // !!! kmk_widg->TreeList()->setCurrentItem( listViewRootItem );
1148 KListViewItem *node = (KListViewItem*) searchFilesListView->firstChild();
1149 if ( !node ) return;
1151 // inform the apropriate method that we are in locate mode:
1152 // set all relevant vars
1153 while ( node )
1155 // as we are called - and this is LOCATE slot - only ONE item in the search
1156 // results should be selected; if there are more - they are ignored
1157 if ( node->isSelected() )
1159 DelSelDir = node->text( 0 );
1160 DelSelFile = node->text( 1 );
1161 locateMode = TRUE;
1162 break;
1164 node = (KListViewItem*) node->nextSibling();
1167 // kdDebug() << " locating " << DelSelDir << " | " << DelSelFile << endl;
1168 QString ci;
1169 // get a list of all listview items to iterate over them
1170 QListViewItemIterator it( treeListView );
1171 while ( it.current() )
1173 QListViewItem *item = it.current();
1174 ci = QString::null;
1175 // this while loop exits when it reaches top level; result is slash ("/") separated path in ci
1176 bool not_found = TRUE;
1177 while ( not_found ) {
1178 ci.prepend( item->text(0) );
1179 if( item->parent() ) { if( item->parent()->text(0).compare("/") != 0 ) ci.prepend( "/" ); item = item->parent(); }
1180 else break;
1182 // kdDebug() << " ci now is " << ci << endl;
1183 item = it.current();
1184 if ( DelSelDir.compare( ci ) == 0 )
1186 treeListView->ensureItemVisible ( item );
1187 if( treeListView->currentItem() == item )
1188 slotTreeListViewCurrentChanged( item );
1189 else
1190 treeListView->setCurrentItem( item );
1191 not_found = FALSE;
1192 break;
1194 ++it;
1196 // switch from search tab to catalog view tab
1197 tabWidget1->setCurrentPage(0);
1200 void kmk::slotSearchButtonClicked()
1202 // if( MusicCatalog.isEmpty() ) return;
1203 QString p = leSearchFor->text();
1204 // TagEditAction->setEnabled( FALSE );
1206 // QTime ptime; ptime.start();
1208 QDateTime dt; long new_rows=0;
1209 // disble table for manipulation : flicker care
1210 searchFilesListView->setEnabled( FALSE );
1211 for( MmDataList::iterator it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1213 if( (!(*it).IsDir()) && ( ((*it).Folder().compare(p)==0) || (((*it).FileName().contains(p,FALSE)>0) ||
1214 ((*it).Artist().contains(p,FALSE)>0) ) || (((*it).Title().contains(p,FALSE)>0) ||
1215 ((*it).Album().contains(p,FALSE)>0) ) ) )
1216 { new_rows++; }
1218 // kdDebug() << new_rows << " rows should be added." << endl;
1219 while( new_rows > searchFilesListView->childCount() )
1220 new KListViewItem( (QListView*) searchFilesListView, 0 );
1221 while( new_rows < searchFilesListView->childCount() ) {
1222 KListViewItem* t = (KListViewItem*) searchFilesListView->lastItem();
1223 if ( t ) delete t;
1225 // kdDebug() << "(kmk):LVupdate: table setup time: " << ptime.elapsed() << " ms." << endl;
1226 // ptime.restart();
1228 if ( new_rows )
1230 new_rows = 0;
1231 KListViewItem * new_item = (KListViewItem*) searchFilesListView->firstChild();
1232 for( MmDataList::iterator it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1234 if( (!(*it).IsDir()) && ( ((*it).Folder().compare(p)==0) || (((*it).FileName().contains(p,FALSE)>0) ||
1235 ((*it).Artist().contains(p,FALSE)>0) ) || (((*it).Title().contains(p,FALSE)>0) ||
1236 ((*it).Album().contains(p,FALSE)>0) ) ) )
1238 if ( !new_item ) qFatal("Something horrible happened!");
1239 new_item->setText( 0, (*it).Folder() ); // folder - for use in play or enqueue file(s) action
1240 new_item->setText( 1, (*it).FileName() ); // file
1241 new_item->setText( 2, (*it).Artist() ); // artist
1242 new_item->setText( 3, (*it).Title() ); // title
1243 new_item->setText( 4, (*it).Album() ); // album
1244 new_item->setText( 5, (*it).Genre() ); // genre
1245 new_item->setText( 6, (*it).Comment() ); // comment
1246 new_item->setText( 7, ((*it).Year()!=0)?QString::number( (*it).Year() ):"" ); // song year
1247 new_item->setText( 8, ((*it).TrackNum()!=0)?QString::number( (*it).TrackNum() ):"" ); // track number
1248 new_item->setText( 9, formatted_string_from_seconds( (*it).Length() ) ); // length
1249 new_item->setText( 10, QString::number( (*it).FileSize() / 1024 ) + " kB" ); // file size
1250 // dt.setTime_t( (*it).ModifiedTime() );
1251 new_item->setText( 11, QString::number( (*it).BitRate()) ); // bitrate
1252 new_item->setText( 12, QString::number( (*it).SampleRate()) ); // samplerate
1253 new_item->setText( 13, QString::number( (*it).Channels()) ); // channels
1254 new_item->setText( 14, "SOME" ); // type
1255 new_item->setText( 15, (*it).IsReadOnly()?"YES":"_no_" ); // read only
1256 new_item = (KListViewItem*) new_item->nextSibling();
1257 new_rows++;
1260 // kdDebug() << new_rows << " rows have been added." << endl;
1261 // update table contets after manipulation
1262 searchFilesListView->clearSelection();
1263 // here we update the first 5 columns' width
1264 Q_CHECK_PTR( d );
1265 // honor user setting whether he/she wants auto-column-width, or likes speed more
1266 if ( d->autoColumnWidth() )
1267 for ( ushort k=0; k<7; k++) searchFilesListView->adjustColumn( k );
1269 searchFilesListView->setEnabled( TRUE );
1270 if(!(new_rows)) KMessageBox::sorry( this, i18n("No files were found, that match your search criterias!"),
1271 i18n("KDE Music Kataloger") );
1274 void kmk::slotClearButtonClicked()
1276 leSearchFor->clear();
1277 searchFilesListView->clear();
1280 void kmk::slotDoneButtonClicked()
1282 filesListView->clearSelection();
1283 tabWidget1->setCurrentPage(0);
1288 /**============================================================================================================**
1289 ** NON SLOTS NON SLOTS NON SLOTS NON SLOTS NON SLOTS NON SLOTS NON SLOTS **
1290 **============================================================================================================**/
1292 void kmk::clearCatalogData( const bool UpdateState )
1294 // clear catalog tree
1295 MusicCatalog.clear();
1296 treeListView->clear();
1297 listViewRootItem = (KListViewItem*) treeListView;
1298 cur_vlItem = listViewRootItem;
1299 // clear table showing files
1300 filesListView->clear();
1301 // clear table showing search result files
1302 searchFilesListView->clear();
1303 // reset stats counters, locate mode helper variables
1304 locateMode = FALSE; DelSelFile = ""; DelSelDir = "";
1305 bytes_to_read = 0; total_bytes = 0; total_files = 0;
1306 total_folders = 0; total_play_time = 0; average_file_size = 0;
1307 // and finally, update state, title and actions accordingly
1308 if( UpdateState ) setCatalogStateAndUpdate( NoCatalog );
1311 void kmk::setCatalogStateAndUpdate( const kmk::CatalogStateEnum state )
1313 CatalogState = state;
1314 switch (state) {
1315 case kmk::NoCatalog:
1316 catalogFileName = QDir::homeDirPath()+"/"+_KMK_NEWCATALOG;
1317 this->setCaption( i18n("No catalog - ") + savedCaption );
1318 fileSaveAction->setEnabled( FALSE );
1319 fileSaveAsAction->setEnabled( FALSE );
1320 fileCatalogFileStats->setEnabled( FALSE );
1321 fileCatalogFileClose->setEnabled( FALSE );
1322 catalogAddNewFolderAction->setEnabled( FALSE );
1323 TagEditAction->setEnabled( TRUE );
1324 listTableLocateAction->setEnabled( FALSE );
1325 playerEnqueueAction->setEnabled( FALSE );
1326 playerEnqueueDirAction->setEnabled( FALSE );
1327 playerEnqueueDirSubdirsAction->setEnabled( FALSE );
1328 playerPlayDirAction->setEnabled( FALSE );
1329 playerPlayDirSubdirsAction->setEnabled( FALSE );
1330 playerPlaySelectionAction->setEnabled( FALSE );
1331 main_container_widget->setEnabled( FALSE );
1332 break;
1333 case kmk::Modified:
1334 this->setCaption( i18n("Catalog [%1][UNSAVED] - ").arg(catalogFileName) + savedCaption );
1335 fileSaveAction->setEnabled( TRUE );
1336 fileSaveAsAction->setEnabled( TRUE );
1337 fileCatalogFileStats->setEnabled( TRUE );
1338 fileCatalogFileClose->setEnabled( TRUE );
1339 catalogAddNewFolderAction->setEnabled( TRUE );
1340 TagEditAction->setEnabled( TRUE );
1341 listTableLocateAction->setEnabled( TRUE );
1342 playerEnqueueAction->setEnabled( TRUE );
1343 playerEnqueueDirAction->setEnabled( TRUE );
1344 playerEnqueueDirSubdirsAction->setEnabled( TRUE );
1345 playerPlayDirAction->setEnabled( TRUE );
1346 playerPlayDirSubdirsAction->setEnabled( TRUE );
1347 playerPlaySelectionAction->setEnabled( TRUE );
1348 main_container_widget->setEnabled( TRUE );
1349 break;
1350 case kmk::Saved:
1351 this->setCaption( i18n("Catalog [%1] - ").arg(catalogFileName) + savedCaption );
1352 fileSaveAction->setEnabled( FALSE );
1353 fileSaveAsAction->setEnabled( TRUE );
1354 fileCatalogFileStats->setEnabled( TRUE );
1355 fileCatalogFileClose->setEnabled( TRUE );
1356 catalogAddNewFolderAction->setEnabled( TRUE );
1357 TagEditAction->setEnabled( TRUE );
1358 listTableLocateAction->setEnabled( TRUE );
1359 playerEnqueueAction->setEnabled( TRUE );
1360 playerEnqueueDirAction->setEnabled( TRUE );
1361 playerEnqueueDirSubdirsAction->setEnabled( TRUE );
1362 playerPlayDirAction->setEnabled( TRUE );
1363 playerPlayDirSubdirsAction->setEnabled( TRUE );
1364 playerPlaySelectionAction->setEnabled( TRUE );
1365 main_container_widget->setEnabled( TRUE );
1366 break;
1367 default:
1368 break;
1372 /* WARNING !!! OBSESIVE USE OF RECURSION!!!!
1374 unsigned long kmk::traverse_tree( const QString& dir )
1376 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
1377 { kmkSmooth->restart(); qApp->processEvents(); }
1378 /* Here we mark our tree depth - subDlevel is a kmk private var */
1379 subDlevel++;
1380 //qWarning( "looking in \""+dir+"\"...");
1381 /* Create a QDir object, set to the "dir-for-scan"
1382 The filter is set to DIRECTORIEs, without symlinks */
1383 QDir d ( dir );
1384 d.setFilter( QDir::Dirs | QDir::NoSymLinks );
1385 /* This gets our "dir-for-scan" subdirs' in @p list */
1386 const QFileInfoList *list = d.entryInfoList();
1387 QFileInfoListIterator it( *list );
1388 /* @p fi is used to get the dir name and last modification time */
1389 QFileInfo *fi = new QFileInfo( d.absPath() );
1390 /* Create a new MmDataList entry with the extracted above data
1391 for the "dir-for-scan" and set @p new_dir to point at it;
1392 we need a pointer to this new item, because if we add any files,
1393 contained in this dir, or its subdirs, we must update its
1394 MmFileSize field according to this below... */
1395 MmDataList::iterator new_dir =
1396 /* Some explanation : as we use the same elements to store info
1397 for audio files, and the dirs containing them - we do this trick:
1398 if the MmData item holds info on DIRECTORY - we set its fields like this:
1399 MmFolder - set to the ABSOLUTE DIR NAME
1400 MmFileName - set to the SHORT DIR NAME
1401 MmIsFolder - set to TRUE
1402 MmReadOnly - wether or not we have permission to write in DIR
1403 MmLength - set to the number of files in "dir-to-scan" subdirs
1404 MmFileSize - set to the number of files contained - updated later */
1405 MusicCatalog.append( MmData( d.absPath(), // folder
1406 d.dirName(), // filename
1407 "", // artist
1408 "", // title
1409 "", // album
1410 "", // genre
1411 "", // comment
1412 0, // year
1413 0, // track number
1414 0, // file size - updated later
1415 fi->lastModified().toTime_t(), // modified time
1416 fi->isWritable(), // read only? - we may use this to determine if we can "rename" dirs
1417 TRUE, // is dir?
1418 0, // audio duration (length) in secs - upd. later
1419 0, // channels
1420 0, // samplerate
1421 0 ) ); // bitrate
1422 /* We are done with @p fi for now - so, get rid of it */
1423 delete fi;
1424 /* Create a new KListView entry, pointed by @p ptr and if it is at
1425 the catalog root make its name the absolute path
1426 to "dir-for-scan", otherwise use short dir name;
1427 we set this item's parent to be what's pointed by kmk's private
1428 var cur_vlItem - it should point to what should be this item's
1429 parent in the catalog tree */
1430 KListViewItem *ptr;
1431 //////////////////////// kdDebug() << "about to crash..." << endl;
1432 if ( subDlevel == 1 ) ptr = new KListViewItem( (QListView*) treeListView, d.absPath() );
1433 else ptr = new KListViewItem( (QListViewItem*) cur_vlItem, d.dirName() );
1434 //////////////////////// kdDebug() << "Strange, did not crash...." << endl;
1435 /* Set the new catalog tree item to be closed, non-expandable:
1436 if it happens to be parent of another - we will fix it later */
1437 ptr->setOpen( FALSE ); ptr->setExpandable( FALSE );
1438 /* Here we initialize our subdirs' loaded files counter @p lf */
1439 unsigned long lf = 0;
1440 /* This checks if "dir-for-scan" has any subdirs */
1441 if ( !list->isEmpty() )
1443 /* And if it has, we go over the list of "dir-for-scan" subdirs */
1444 while ( (fi = it.current()) != 0 )
1446 /* Check if these subdirs are not the FS reserved "." and ".."
1447 FIX: AND that we can also access them !!! */
1448 if ( (fi->isReadable()) && (fi->isExecutable()) )
1449 if ( (fi->fileName().compare(".")!=0) && (fi->fileName().compare("..")!=0) )
1451 /* So, we've found a valid "dir-for-scan" subdirectory.
1452 If you remember - we used kmk's private var cur_vlItem as a parent
1453 of the KListViewItem we added earlier; so we want it to be
1454 "preset" when we are called, don't we? Let's make sure it is! */
1455 cur_vlItem = ptr;
1456 /* We are setup and ready to call ourselves recursievely on this one.
1457 Because at the end of traverse_tree we provide a count of the
1458 actually added files to the catalog, including the added files
1459 from each subdir, lets increase @p lf with the numer of files in
1460 the directory we want to check...
1461 Here is the tracking : */
1462 lf += traverse_tree( d.filePath( fi->fileName() ) );
1464 /* Then go to the next in the list */
1465 ++it;
1467 /* We finished calling ourselves recursievly. So, if the subdirs'
1468 scan added any files - set the new catalog item to represent this
1469 accordingly - we hold subentries, so we need to be expandable;
1470 Maybe the user could set wheter or not the new catalog is already
1471 open or collapsed */
1472 if ( lf > 0 ) { ptr->setOpen( FALSE ); ptr->setExpandable( TRUE ); }
1473 //qWarning("having "+QString::number(lf)+" files reported back...");
1475 /* Then we set our "dir-for-scan" filter to files, without symlinks */
1476 d.setFilter( QDir::Files | QDir::NoSymLinks );
1477 /* And get a list of its contents, after the filtering is apllied */
1478 const QFileInfoList *list2 = d.entryInfoList();
1479 QFileInfoListIterator it2( *list2 );
1480 QFileInfo *fi2;
1481 /* These we will use for storage of the exctracted data from files */
1482 QString art, ttl, alb, gen, cmt;
1483 /* This will be our "dir-for-scan" ONLY (i.e. without including
1484 the count from the subdirs) loaded files counter; zero-init it.
1485 Nowadays there are file systems, capable of handling enormous amounts
1486 of files in a single directory - make sure we don't choke on someones
1487 huge collection, by using extra large counter.*/
1488 unsigned long loaded_files = 0;
1489 /* A cycle to iterate over the files in "dir-for-scan" */
1490 while ( (fi2 = it2.current()) != 0 )
1492 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
1493 { kmkSmooth->restart(); qApp->processEvents(); }
1494 /* If the files aren't with the supported extensions - ignore them;
1495 We call QFileInfo->extension(FALSE) because we need only chars after LAST '.' */
1496 if ( fi2->isReadable() )
1497 if ( ( fi2->extension(FALSE).lower().compare("mp3") == 0 ) ||
1498 ( fi2->extension(FALSE).lower().compare("ogg") == 0 ) ||
1499 /* Is it correct for a FLAC file to have any other extension than "flac"?
1500 For example - can it be named "my_audio_file.fla"? Ideas, advices.... */
1501 ( fi2->extension(FALSE).lower().compare("flac") == 0 ) )
1503 /* Init our storage for this file... */
1504 art = ""; ttl = ""; alb = ""; gen = ""; cmt = "";
1505 using namespace TagLib;
1506 /* Use TagLib's functions to extract meta data and audio properties;
1507 This tells TagLib to read tags, and be as fast as possible */
1508 ///@@@@@@@@@@@@@
1509 #ifdef _KMK_DEBUG__
1510 kdDebug() << " Now checking: " << d.filePath( fi2->fileName().latin1() ) << endl;
1511 #endif
1512 ///@@@@@@@@@@@@@
1513 TagLib::FileRef f( d.filePath( fi2->fileName().latin1() ), TRUE, TagLib::AudioProperties::Fast );
1514 /* If TagLib found the file to be valid - then add it to the catalog.
1515 Maybe here would be a good place to update some global catalog stats,
1516 like total storage used by the collection we are cataloguining, number
1517 of files in it, average bitrate, highest one, lowest one, biggest file,
1518 smallest file... and anything else someone might consider "interesting"!*/
1519 if( f.file()->isValid() )
1521 TagLib::String s;
1522 s = f.tag()->artist(); art = s.toCString();
1523 s = f.tag()->title(); ttl = s.toCString();
1524 s = f.tag()->album(); alb = s.toCString();
1525 s = f.tag()->genre(); gen = s.toCString();
1526 s = f.tag()->comment(); cmt = s.toCString();
1527 /* Some of the extracted file data is in our storage vars now.
1528 Create a new MmData item, and fill it with what we've read. */
1529 MmData ni = MmData();
1530 ni.setFolder( d.absPath() );
1531 ni.setFileName( fi2->fileName() );
1532 ni.setArtist( art );
1533 ni.setTitle( ttl );
1534 ni.setAlbum( alb );
1535 ni.setGenre( gen );
1536 ni.setComment( cmt );
1537 ni.setYear( f.tag()->year() );
1538 ni.setTrackNum( f.tag()->track() );
1539 ni.setFileSize( fi2->size() );
1540 // qDebug( "To "+QString::number( total_bytes )+" B, adding "+QString::number((Q_ULLONG) fi2->size())+" B." );
1541 total_bytes += (Q_ULLONG) fi2->size();
1542 ni.setModifiedTime( fi2->lastModified().toTime_t() );
1543 /* How about using Qt's functions QFile and QDir to check this?
1544 Not a problem! I've checked it - TagLib gives correct data; in a bunch of mp3s, I
1545 set to 2 of them to have only read acces - TagLib said exactly this! The only thing
1546 left in mind is speed, but... how do I check IT? Pl.Petrov */
1547 ni.setIsReadOnly( f.file()->readOnly() );
1548 ni.setIsDir( FALSE ); // we are adding a file
1549 ni.setLength( f.audioProperties()->length() ); total_play_time += ni.Length();
1550 ni.setChannels( f.audioProperties()->channels() );
1551 ni.setSampleRate( f.audioProperties()->sampleRate() );
1552 ni.setBitRate( f.audioProperties()->bitrate() );
1553 /* Now, after we've filled our new catalog item - lets add it
1554 to the catalog list */
1555 MusicCatalog.append( ni );
1556 /* We said we will keep track of loaded files number - do so */
1557 loaded_files++; total_files++;
1560 /* Go to the next file in "dir-for-scan"... */
1561 ++it2;
1563 /* Now here is what we've got so far:
1564 o) new_dir - it is a pointer to the MmData item,
1565 containing the "dir-for-scan" directory info;
1566 o) ptr - a pointer the KListViewItem, which we created and added to the catalog tree;
1567 o) lf - contains the number of files added from all "dir-for-scan" SUBDIRs;
1568 o) loaded_files - contains the number of files added from "dir-for-scan" itself;
1569 If any of the last two in the list above is non-zero... */
1570 if ( loaded_files || lf )
1571 /* ...we must update the MmData item, containing the info for "dir-for-scan" to comply with this:
1572 [ ...if a MmData item holds info for a directory,
1573 its MmFileSize field holds a count of the files contained
1574 in it, !!! NOT !!! including its subdirs. ]
1575 We also update our overall folder counter.*/
1576 { (*new_dir).setFileSize( loaded_files ); (*new_dir).setLength( lf ); total_folders++; }
1577 /* If the sum of the last two is zero (0), or... lets put it this way -
1578 if both of them are zeros - then the first two ought to be removed; */
1579 else { MusicCatalog.remove( new_dir ); delete( ptr ); }
1580 /* We are done on this level - return subDlevel to where it was... */
1581 subDlevel--;
1582 /* And then return the sum of files in "dir-for-scan" and the files in
1583 its subdirectories.*/
1584 return (loaded_files + lf);
1587 /* WARNING !!! OBSESIVE USE OF RECURSION!!!!
1589 void kmk::bytes_to_read_by_traverse( const QString& dir )
1591 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
1592 { kmkSmooth->restart(); qApp->processEvents(); }
1593 // kdDebug() << "called with param " << dir << endl;
1594 QDir d ( dir ); d.setFilter( QDir::Dirs | QDir::NoSymLinks );
1595 const QFileInfoList *list = d.entryInfoList(); QFileInfoListIterator it( *list );
1596 QFileInfo *fi = new QFileInfo();
1597 if ( !list->isEmpty() )
1598 while ( (fi = it.current()) != 0 )
1600 if ( (fi->isReadable()) && (fi->isExecutable()) )
1601 if ( (fi->fileName().compare(".")!=0) && (fi->fileName().compare("..")!=0) )
1603 if ( !fi->isDir() ) kdDebug() << "...and we are trying to do dir op on non-dir!" << endl;
1604 else bytes_to_read_by_traverse( d.filePath( fi->fileName() ) );
1606 ++it;
1608 d.setFilter( QDir::Files | QDir::NoSymLinks ); const QFileInfoList *list2 = d.entryInfoList();
1609 QFileInfoListIterator it2( *list2 ); QFileInfo *fi2;
1610 while ( (fi2 = it2.current()) != 0 )
1612 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
1613 { kmkSmooth->restart(); qApp->processEvents(); }
1614 if ( fi2->isReadable() )
1615 if ( ( fi2->extension(FALSE).lower().compare("mp3") == 0 ) ||
1616 ( fi2->extension(FALSE).lower().compare("ogg") == 0 ) ||
1617 ( fi2->extension(FALSE).lower().compare("flac") == 0 ) )
1618 { bytes_to_read += (Q_ULLONG) fi2->size(); files_to_read++; }
1619 ++it2;
1623 /* Some neat code I am really proud of: fast and effective; doesn't tollerate storage errors, though... */
1624 uint kmk::generateListViewSubtreeXML( const KListViewItem *item, QDomDocument doc, QDomElement e, const uint add_lv )
1626 if( item != 0 )
1628 // kdDebug() << "DOMelem <tree_item Name=\"" << item->text(0) <<"\" M=\"" << add_lv << "\" />" << endl;
1630 QDomElement dscr =
1631 doc.createElement( "FolderTreeDescriptor" );
1632 dscr.setAttribute( "FolderName", item->text(0) );
1633 dscr.setAttribute( "GoUpHowMuch", add_lv );
1634 e.appendChild( dscr );
1636 uint levels_added=0;
1637 KListViewItem * some_child = (KListViewItem*) item->firstChild();
1638 while( some_child )
1640 levels_added = generateListViewSubtreeXML( some_child, doc, e, levels_added );
1641 some_child = (KListViewItem*) some_child->nextSibling();
1644 return ++levels_added;
1646 else return 0;
1649 void kmk::generateListViewXML( const KListView *list, QDomDocument doc, QDomElement e )
1651 if( list != 0 )
1653 uint levels_added=0;
1654 KListViewItem * some_child = (KListViewItem*) list->firstChild();
1655 while( some_child )
1657 levels_added = generateListViewSubtreeXML( some_child, doc, e, levels_added );
1658 some_child = (KListViewItem*) some_child->nextSibling();
1665 * Finds all files in TreeList currentItem and all its subfolders
1666 * and passes them to player_bin with parameter @param act
1667 * NOTE this function respects _kmk_include_subdirs
1668 * NOTE 2: in a rethink of what the function should do,
1669 * the new behaviour is that it writes all the relevant files
1670 * in a .M3U or .PLS playlist file (stored with a randomly generated
1671 * name in /tmp/kmk) and passes it to the player with @p act
1673 void kmk::playerDir( QString act )
1675 QListViewItem* lv = treeListView->currentItem();
1676 if ( lv != 0 )
1678 // generate some random based filename for the playlist file
1679 QString flNm = "/tmp/kmk/kmk_lst_"+QString::number( random() )+".m3u";
1680 QFile file( flNm );
1681 if ( file.open( IO_WriteOnly ) )
1683 QTextStream stream( &file );
1684 stream << "#EXTM3U\n";
1685 // determine the folder we use as root and save it in @p d
1686 QString d;
1687 while ( TRUE ) {
1688 d.prepend( lv->text(0) );
1689 if( lv->parent() ) { if( lv->parent()->text(0).compare("/") != 0 ) d.prepend( "/" ); lv = lv->parent(); }
1690 else break;
1692 // do the acctual adding of files to the playlist....
1693 MmDataList::iterator it;
1694 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1695 if ( _kmk_include_subdirs )
1697 if ( ( !((*it).IsDir()) ) && ( (( (*it).Folder().compare(d)==0 )) || ( (*it).Folder().contains(d+"/",TRUE)==1 ) ) )
1699 stream << "#EXTINF:" << (*it).Length() << " ," << (*it).Artist() << " - " << (*it).Title() << "\n";
1700 stream << (*it).Folder()+"/"+(*it).FileName() << "\n";
1703 else
1705 if ( ( !((*it).IsDir()) ) && ( (*it).Folder().compare(d)==0 ) )
1707 stream << "#EXTINF:" << (*it).Length() << " ," << (*it).Artist() << " - " << (*it).Title() << "\n";
1708 stream << (*it).Folder()+"/"+(*it).FileName()<<"\n";
1711 file.close();
1712 QProcess* ext_prog = new QProcess( this, "player_ext");
1713 ext_prog->clearArguments();
1714 ext_prog->addArgument( player_bin );
1715 // action parameter
1716 ext_prog->addArgument( act );
1717 ext_prog->addArgument( flNm );
1718 ext_prog->launch( "" );
1719 delete ext_prog;
1720 // file.remove();
1722 else QMessageBox::warning( this, i18n("KDE Music Kataloger"),i18n("Could not open %1 for writing!").arg(flNm),
1723 QMessageBox::Abort,QMessageBox::NoButton,QMessageBox::NoButton );
1727 MmData* kmk::findMmData( const QString& folder, const QString& filename )
1729 bool found = FALSE;
1730 MmDataList::iterator it;
1731 for( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1733 if( ((*it).Folder().compare(folder)==0) && ((*it).FileName().compare(filename)==0) )
1735 found = TRUE;
1736 // kdDebug()<<"called once: find MmData"<<endl;
1737 break;
1740 if( found ) return &(*it);
1741 else return 0;
1744 const QString kmk::formatted_string_from_seconds( const Q_ULLONG t )
1746 QString ttime = "";
1747 unsigned short secs, mins, hours, years;
1748 secs = mins = hours = years = 0;
1749 uint days = 0;
1751 if ( t == 0 ) return ttime;
1752 years = t / 30758400;
1753 days = (t - (years * 30758400)) / 86400;
1754 hours = (t - ((years * 30758400) + (days * 86400))) / 3600;
1755 mins = (t - ((years * 30758400) + (days * 86400) + (hours * 3600))) / 60;
1756 secs = t % 60;
1757 if ( years ) if ( years > 1 ) ttime.append( i18n("%1 years").arg(years) );
1758 else ttime.append( i18n("1 year") );
1759 if ( days )
1761 if ( years ) ttime.append(", ");
1762 if ( days > 1 ) ttime.append( i18n("%1 days").arg(days) );
1763 else ttime.append( i18n("1 day") );
1765 if ( (days || years) && (hours || mins || secs) ) ttime.append(", ");
1766 if ( hours )
1768 if ( hours>9 ) ttime.append( QString("%1:").arg(hours) );
1769 else ttime.append( QString("0%1:").arg(hours) );
1771 if ( secs || mins || hours )
1773 if ( mins>9 ) ttime.append( QString("%1:").arg(mins) );
1774 else ttime.append( QString("0%1:").arg(mins) );
1776 if ( secs || mins || hours )
1778 if ( secs>9 ) ttime.append( QString("%1").arg(secs) );
1779 else ttime.append( QString("0%1").arg(secs) );
1781 return ttime;
1784 const bool kmk::catalog_has_dir( const QString & looked_for )
1786 bool found = FALSE;
1787 MmDataList::iterator it;
1789 // QDir gives us paths ending on "/" and in the catalog - folders don't
1790 // end on "/"; so, strip that last slash "/" symbol
1791 QString stripped_looked_for = looked_for;
1792 if ( stripped_looked_for.length()>1 )
1793 stripped_looked_for.setLength( stripped_looked_for.length() - 1 );
1794 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1796 if ( (*it).IsDir() )
1798 if ( (*it).Folder().compare( stripped_looked_for )==0 )
1800 found = TRUE;
1801 break;
1806 return found;
1809 void kmk::loadCatalog( const QString & fileName )
1811 QDomDocument doc = QDomDocument::QDomDocument();
1812 QFile file( fileName );
1813 if ( !file.open( IO_ReadOnly | IO_Raw ) ) {
1814 QMessageBox::warning( this, i18n("KDE Music Kataloger"),i18n("Could not open %1 for reading!").arg(fileName),
1815 QMessageBox::Abort,QMessageBox::NoButton,QMessageBox::NoButton );
1816 return;
1818 this->setCaption( i18n("Checking integrity of [%1] - ").arg(fileName) );
1819 this->repaint(); qApp->processEvents();
1820 // sleep( 5 ); kdDebug() << " sleeping..." << endl;
1821 QString xml_parse_err = "test"; int err_ln = 0; int err_cl = 0;
1822 #ifdef _KMK_DEBUG__
1823 QTime parse_time; parse_time.start();
1824 #endif
1825 if ( !doc.setContent( &file, TRUE, &xml_parse_err, &err_ln, &err_cl ) ) {
1826 file.close();
1827 // qWarning( QString::number( *err_ln )+ " " + QString::number( *err_cl ) );
1828 QMessageBox::warning( this, i18n("KDE Music Kataloger"),
1829 i18n("Error parsing catalog file %1 !\n MSG: %2 on line %3, column %4.")
1830 .arg(fileName).arg(xml_parse_err.ascii()).arg(QString::number( err_ln )).arg(QString::number( err_cl )),
1831 QMessageBox::Abort,QMessageBox::NoButton,QMessageBox::NoButton );
1832 this->setCaption( savedCaption );
1833 return;
1835 file.close();
1836 #ifdef _KMK_DEBUG__
1837 kdDebug() << "(kmk):parse_file(SAX2 parse): elapsed time: " << parse_time.elapsed() << " ms." << endl;
1838 parse_time.restart();
1839 #endif
1840 clearCatalogData();
1841 this->setCaption( i18n("Loading [%1]...").arg(fileName) );
1842 catalogFileName = fileName;
1843 qApp->processEvents();
1845 /** READ CATALOG VERSION INFO - if none is found - inform, and bail out */
1846 bool process = TRUE;
1847 ulong nodes_to_add = 0;
1848 QDomNodeList ml = doc.elementsByTagName( "KMK_catalog_file_data" ); QDomNode n; QDomElement e; QDomAttr a;
1849 for ( uint i = 0; i<ml.count(); i++)
1851 n = ml.item( i );
1852 if ( n.isElement() )
1853 { e = n.toElement();
1854 QString tx = e.attribute("Version");
1855 if( !tx.isNull() )
1857 if( tx.toInt() > 1 ) KMessageBox::sorry( this, i18n("The catalog [%1] is saved in new format (v.%2),"
1858 "so some data will not be recognized.").arg(fileName).arg(tx.toInt()),i18n("KDE Music Kataloger") );
1859 if( tx.toInt() == 0 ) process = FALSE;
1863 #ifdef _KMK_DEBUG__
1864 kdDebug() << "(kmk):parse_file(new read): catalog version read time: " << parse_time.elapsed() << " ms." << endl;
1865 parse_time.restart();
1866 #endif
1868 if( process )
1870 ml = doc.elementsByTagName( "CatalogFile_Statistics" );
1871 for ( uint i = 0; i<ml.count(); i++)
1873 n = ml.item( i );
1874 if ( n.isElement() )
1875 { e = n.toElement();
1876 QString tx = e.attribute("Total_files_count");
1877 if( !tx.isNull() ) total_files = (Q_ULLONG) tx.toDouble();
1878 tx = e.attribute("Total_bytes_count");
1879 if( !tx.isNull() ) total_bytes = (Q_ULLONG) tx.toDouble();
1880 tx = e.attribute("Total_dirs_count");
1881 if( !tx.isNull() ) total_folders = (Q_ULLONG) tx.toDouble();
1882 tx = e.attribute("Total_secs_count");
1883 if( !tx.isNull() ) total_play_time = (Q_ULLONG) tx.toDouble();
1884 tx = e.attribute("Average_file_size");
1885 if( !tx.isNull() ) average_file_size = (Q_ULLONG) tx.toDouble();
1888 #ifdef _KMK_DEBUG__
1889 kdDebug() << "(kmk):parse_file(new read): catalog stats read time: " << parse_time.elapsed() << " ms." << endl;
1890 parse_time.restart();
1891 #endif
1894 /** READ THE FOLDERS STRUCTURE AUXILARY DATA - version, counters, etc */
1895 /* Currently - IRRELEVANT
1896 ml = doc.elementsByTagName( "CatalogFile_TreeDescription" );
1897 for ( uint i = 0; i<ml.count(); i++)
1899 n = ml.item( i );
1900 if ( n.isAttr() )
1901 { a = n.toAttr();
1902 #ifdef __KMK_DEBUG
1903 kdDebug() << "(kmk): i="<<i<<"; found attr; name: "<< a.name()<<"; value: "<< a.value() << endl;
1904 #endif
1906 if ( n.isElement() )
1907 { e = n.toElement();
1908 #ifdef __KMK_DEBUG
1909 kdDebug() << "(kmk): i="<<i<<"; found elem; tag: "<< e.tagName() << endl;
1910 #endif
1915 /** READ THE FOLDERS STRUCTURE AND RECREATE IT IN KLISTVIEW */
1916 if( process )
1918 KListViewItem *CURRENT = 0; bool warned = FALSE;
1919 ml = doc.elementsByTagName( "FolderTreeDescriptor" );
1920 for ( uint i = 0; i<ml.count(); i++)
1922 n = ml.item( i );
1923 if ( n.isElement() )
1924 { e = n.toElement();
1925 QString nm = e.attribute("FolderName");
1926 if( nm.isNull() ) kdDebug() << "READ A NULL FOLDER NAME!" << endl;
1927 QString up = e.attribute("GoUpHowMuch");
1928 if( up.isNull() ) kdDebug() << "READ A NULL GO_UP_HOW_MUCH!"<< endl;
1929 uint t = up.toUInt();
1930 while( (t) && (CURRENT) )
1932 //kdDebug() << i << ":going up from " << CURRENT->text(0) << "; parent() is: " << CURRENT->parent() << endl;
1933 CURRENT = (KListViewItem*)CURRENT->parent();
1934 t--;
1936 if( t && (!warned) ) { KMessageBox::sorry( this, i18n("The catalog file you are trying to load is messed up. "
1937 "You get this message, so it passed XML sanity checks. That means almost for sure "
1938 "that it has been edited by hand - you are better off if you recreate it via Catalog->New. "
1939 "The scanning is fast enough anyway (around 10GB mp3 files scanned per minute)!\n"
1940 "You have been warned - don't complain if something goes wrong."), i18n("KDE Music Kataloger") );
1941 warned = TRUE; }
1942 //if(CURRENT)
1943 // kdDebug() << i << ":(t="<<t<<") adding "<<nm<<" at "<<CURRENT->text(0)<<endl;
1944 //else
1945 // kdDebug() << i << ":(t="<<t<<") adding "<<nm<<" -..---~~~'''''''~~~~---- PARENT IS THE LIST VIEW ITSELF!!!!"<<endl;
1946 if(CURRENT) CURRENT = new KListViewItem( (QListViewItem*) CURRENT, nm );
1947 else CURRENT = new KListViewItem( (QListView*) treeListView, nm );
1948 //kdDebug() << i << ":-------------> "<<CURRENT->text(0)<<"'s parent() is: " << CURRENT->parent() << endl;
1951 #ifdef _KMK_DEBUG__
1952 kdDebug() << "(kmk):parse_file(new read): folder tree reconstruction time: " << parse_time.elapsed() << " ms." << endl;
1953 parse_time.restart();
1954 #endif
1955 qApp->processEvents();
1958 /** READ CATALOG ITEMS AUXILARY DATA - objects number, size, etc */
1959 if( process )
1961 ml = doc.elementsByTagName( "CatalogFile_Objects" );
1962 for ( uint i = 0; i<ml.count(); i++)
1964 n = ml.item( i );
1965 if ( n.isElement() )
1966 { e = n.toElement();
1967 QString tx = e.attribute("Total_MObjs_count");
1968 if( !tx.isNull() ) nodes_to_add = tx.toULong();
1971 #ifdef _KMK_DEBUG__
1972 kdDebug() << "(kmk):parse_file(new read): catalog objects count read time: " << parse_time.elapsed() << " ms." << endl;
1973 parse_time.restart();
1974 #endif
1977 /** READ ACTUAL AUDIO FILE DATA INTO MusicCatalog */
1978 if( process )
1980 ml = doc.elementsByTagName( "MObj" ); MmData node;
1981 for ( uint i = 0; i<ml.count(); i++)
1983 uint tags_found = 0;
1984 n = ml.item( i );
1985 if ( n.isElement() )
1986 { e = n.toElement(); QString
1987 tx = e.attribute("Folder"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
1988 else { node.setFolder( tx ); tags_found++; }
1989 tx = e.attribute("Filename"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
1990 else { node.setFileName( tx ); tags_found++; }
1991 tx = e.attribute("Artist"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
1992 else { node.setArtist( tx ); tags_found++; }
1993 tx = e.attribute("Title"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
1994 else { node.setTitle( tx ); tags_found++; }
1995 tx = e.attribute("Album"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
1996 else { node.setAlbum( tx ); tags_found++; }
1997 tx = e.attribute("Genre"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
1998 else { node.setGenre( tx ); tags_found++; }
1999 tx = e.attribute("Comment"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2000 else { node.setComment( tx ); tags_found++; }
2001 tx = e.attribute("Year"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2002 else { node.setYear( tx.toInt() ); tags_found++; }
2003 tx = e.attribute("TrackNumber"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2004 else { node.setTrackNum( tx.toInt() ); tags_found++; }
2005 tx = e.attribute("Length"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2006 else { node.setLength( tx.toInt() ); tags_found++; }
2007 tx = e.attribute("Modified"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2008 else { node.setModifiedTime( tx.toInt() ); tags_found++; }
2009 tx = e.attribute("Size"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2010 else { node.setFileSize( tx.toInt() ); tags_found++; }
2011 tx = e.attribute("Channels"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2012 else { node.setChannels( tx.toInt() ); tags_found++; }
2013 tx = e.attribute("BitRate"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2014 else { node.setBitRate( tx.toInt() ); tags_found++; }
2015 tx = e.attribute("SampleRate"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2016 else { node.setSampleRate( tx.toInt() ); tags_found++; }
2017 tx = e.attribute("IsReadOnly"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2018 else { node.setIsReadOnly( (tx.compare("YES")==0) ? TRUE:FALSE ); tags_found++; }
2019 tx = e.attribute("IsFolder"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2020 else { node.setIsDir( (tx.compare("YES")==0) ? TRUE:FALSE ); tags_found++; }
2022 if ( tags_found ) MusicCatalog.append( node );
2024 #ifdef _KMK_DEBUG__
2025 kdDebug() << "(kmk):parse_file(new read): node read and add time: " << parse_time.elapsed() << " ms." << endl;
2026 parse_time.restart();
2027 #endif
2030 if( ! process )
2031 KMessageBox::sorry( this, i18n("No catalog markings found in [%1].\nThis can happen if the file is corrupt.")
2032 .arg(catalogFileName),i18n("KDE Music Kataloger") );
2033 else {
2034 setCatalogStateAndUpdate( Saved );
2035 fileCatalogFileStats->setEnabled( TRUE );
2036 fileCatalogFileClose->setEnabled( TRUE );
2041 #include "kmk.moc"