juk.cpp 19.9 KB
Newer Older
1 2 3 4 5
/***************************************************************************
                          juk.cpp  -  description
                             -------------------
    begin                : Mon Feb  4 23:40:41 EST 2002
    copyright            : (C) 2002 by Scott Wheeler
6
    email                : wheeler@kde.org
7
***************************************************************************/
8 9 10 11 12 13 14 15 16 17

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

18
#include <kapplication.h>
19
#include <klocale.h>
20
#include <kiconloader.h>
21
#include <kcmdlineargs.h>
22
#include <kstatusbar.h>
23
#include <kconfig.h>
24
#include <kdebug.h>
25
#include <kmessagebox.h>
26

27 28
#include <qtimer.h>
#include <qlistview.h>
29
#include <qinputdialog.h>
30
#include <qslider.h>
31 32
#include <qstrlist.h>
#include <qmetaobject.h>
33

34
#include "juk.h"
35
#include "slideraction.h"
36
#include "cache.h"
37
#include "statuslabel.h"
38 39
#include "splashscreen.h"
#include "genrelisteditor.h"
40
#include "systemtray.h"
41 42 43 44 45

////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

46
JuK::JuK(QWidget *parent, const char *name) : KMainWindow(parent, name, WDestructiveClose)
47
{
48 49 50
    SplashScreen::instance()->show();
    kapp->processEvents();
 
51 52
    // Expect segfaults if you change this order.

53
    readSettings();
54
    setupLayout();
55
    setupActions();
56
    slotPlaylistChanged();
57
    readConfig();
58
    setupPlayer();
59
    setupSystemTray();
60
    processArgs();
61

62
    SplashScreen::finishedLoading();
Scott Wheeler's avatar
Scott Wheeler committed
63
    QTimer::singleShot(0, CollectionList::instance(), SLOT(slotCheckCache()));
64 65 66 67
}

JuK::~JuK()
{
68

69 70 71 72 73 74
}

////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////

75 76
void JuK::setupLayout()
{
77 78
    m_splitter = new PlaylistSplitter(this, m_restore, "playlistSplitter");
    setCentralWidget(m_splitter);
79 80

    // playlist item activation connection
81 82
    connect(m_splitter, SIGNAL(signalDoubleClicked()), this, SLOT(slotPlaySelectedFile()));
    connect(m_splitter, SIGNAL(signalListBoxDoubleClicked()), this, SLOT(slotPlayFirstFile()));
83

84
    // create status bar
85 86
    m_statusLabel = new StatusLabel(statusBar());
    statusBar()->addWidget(m_statusLabel, 1);
87

88 89
    connect(m_splitter, SIGNAL(signalSelectedPlaylistCountChanged(int)), m_statusLabel, SLOT(setPlaylistCount(int)));
    connect(m_statusLabel, SIGNAL(jumpButtonClicked()), m_splitter, SLOT(slotSelectPlaying()));
90

91
    m_splitter->setFocus();
92

93
    resize(750, 500);
94 95
}

96 97
void JuK::setupActions()
{
98
    // file menu
99 100 101
    KStdAction::openNew(m_splitter, SLOT(slotCreatePlaylist()), actionCollection());
    KStdAction::open(m_splitter, SLOT(slotOpen()), actionCollection());
    new KAction(i18n("Open &Directory..."), "fileopen", 0, m_splitter, SLOT(slotOpenDirectory()), actionCollection(), "openDirectory");
102

103
    m_renamePlaylistAction = new KAction(i18n("Rename..."), 0, m_splitter, SLOT(slotRenamePlaylist()), 
104
				       actionCollection(), "renamePlaylist");
105
    new KAction(i18n("Duplicate..."), "editcopy", 0, m_splitter, SLOT(slotDuplicatePlaylist()), actionCollection(), "duplicatePlaylist");
106
    
107 108 109
    m_savePlaylistAction = KStdAction::save(m_splitter, SLOT(slotSavePlaylist()), actionCollection());
    m_saveAsPlaylistAction = KStdAction::saveAs(m_splitter, SLOT(slotSaveAsPlaylist()), actionCollection());
    m_deleteItemPlaylistAction = new KAction(i18n("Remove"), "edittrash", 0, m_splitter, SLOT(slotDeletePlaylist()), 
110 111
					   actionCollection(), "deleteItemPlaylist");

112 113
    //KStdAction::quit(this, SLOT(close()), actionCollection());
    KStdAction::quit(kapp, SLOT(quit()), actionCollection());
114 115

    // edit menu
116
    KStdAction::cut(this, SLOT(cut()), actionCollection());
117 118 119 120
    KStdAction::copy(this, SLOT(copy()), actionCollection());
    KStdAction::paste(this, SLOT(paste()), actionCollection());
    new KAction(i18n("Clear"), "editclear", 0, this, SLOT(clear()), actionCollection(), "clear");
    KStdAction::selectAll(this, SLOT(selectAll()), actionCollection());
121

122
    // view menu
123 124 125 126
    m_showEditorAction = new KToggleAction(i18n("Show Tag Editor"), "edit", 0, actionCollection(), "showEditor");
    connect(m_showEditorAction, SIGNAL(toggled(bool)), m_splitter, SLOT(slotSetEditorVisible(bool)));
    KStdAction::redisplay(m_splitter, SLOT(slotRefresh()), actionCollection());
    actionCollection()->insert(m_splitter->columnVisibleAction());
127
    
128
    // play menu
129
    m_randomPlayAction = new KToggleAction(i18n("Random Play"), 0, actionCollection(), "randomPlay");
130 131 132 133 134
    m_playAction = new KAction(i18n("&Play"), "player_play", 0, this, SLOT(slotPlay()), actionCollection(), "play");
    m_pauseAction = new KAction(i18n("P&ause"), "player_pause", 0, this, SLOT(slotPause()), actionCollection(), "pause");
    m_stopAction = new KAction(i18n("&Stop"), "player_stop", 0, this, SLOT(slotStop()), actionCollection(), "stop");
    m_backAction = new KAction(i18n("Skip &Back"), "player_start", 0, this, SLOT(slotBack()), actionCollection(), "back");
    m_forwardAction = new KAction(i18n("Skip &Forward"), "player_end", 0, this, SLOT(slotForward()), actionCollection(), "forward");
135

136
    // tagger menu
137 138
    new KAction(i18n("Save"), "filesave", "CTRL+t", m_splitter, SLOT(slotSaveTag()), actionCollection(), "saveItem");
    new KAction(i18n("Delete"), "editdelete", 0, m_splitter, SLOT(slotDeleteSelectedItems()), actionCollection(), "removeItem");
139
    
140
    // settings menu
141 142 143 144

    new KToggleAction(i18n("Show Menu Bar"), "CTRL+m", this, SLOT(slotToggleMenuBar()), actionCollection(), "toggleMenuBar");
    new KToggleAction(i18n("Show Tool Bar"), "CTRL+b", this, SLOT(slotToggleToolBar()), actionCollection(), "toggleToolBar");

145
    m_restoreOnLoadAction = new KToggleAction(i18n("Restore Playlists on Load"),  0, actionCollection(), "restoreOnLoad");
146

147
    m_toggleSystemTrayAction = new KToggleAction(i18n("Dock in System Tray"), 0, actionCollection(), "toggleSystemTray");
148
    connect(m_toggleSystemTrayAction, SIGNAL(toggled(bool)), this, SLOT(slotToggleSystemTray(bool)));
149

150
    new KAction(i18n("Genre List Editor..."), 0, this, SLOT(slotShowGenreListEditor()), actionCollection(), "showGenreListEditor");
151

152

153 154 155
    m_outputSelectAction = Player::playerSelectAction(actionCollection());
    if(m_outputSelectAction) {
	m_outputSelectAction->setCurrentItem(0);
156
	connect(m_outputSelectAction, SIGNAL(activated(int)), this, SLOT(slotSetOutput(int)));
157
    }
158

159
    // just in the toolbar
160
    m_sliderAction = new SliderAction(i18n("Track Position"), actionCollection(), "trackPositionAction");
161 162

    createGUI();
163 164

    // set the slider to the proper orientation and make it stay that way
165 166
    m_sliderAction->slotUpdateOrientation();
    connect(this, SIGNAL(dockWindowPositionChanged(QDockWindow *)), m_sliderAction, SLOT(slotUpdateOrientation(QDockWindow *)));
167

168
    connect(m_splitter, SIGNAL(signalPlaylistChanged()), this, SLOT(slotPlaylistChanged()));
169 170
}

171
void JuK::setupSystemTray()
172
{
173 174 175
    if(m_toggleSystemTrayAction && m_toggleSystemTrayAction->isChecked()) {
	m_systemTray = new SystemTray(this, "systemTray");
	m_systemTray->show();
176
	
177 178 179 180 181
	connect(m_systemTray, SIGNAL(signalPlay()),    this, SLOT(slotPlay()));
	connect(m_systemTray, SIGNAL(signalStop()),    this, SLOT(slotStop()));
	connect(m_systemTray, SIGNAL(signalPause()),   this, SLOT(slotPause()));
	connect(m_systemTray, SIGNAL(signalBack()),    this, SLOT(slotBack()));
	connect(m_systemTray, SIGNAL(signalForward()), this, SLOT(slotForward()));
182

183
	connect(this, SIGNAL(signalNewSong(const QString&)), m_systemTray, SLOT(slotNewSong(const QString&)));
184
	
185 186 187 188
	if(m_player && m_player->paused())
	    m_systemTray->slotPause();
	else if(m_player && m_player->playing())
	    m_systemTray->slotPlay();
189 190
    }
    else
191
	m_systemTray = 0;
192 193
}

194 195
void JuK::setupPlayer()
{
196 197 198 199 200 201
    m_trackPositionDragging = false;
    m_noSeek = false;
    m_pauseAction->setEnabled(false);
    m_stopAction->setEnabled(false);
    m_backAction->setEnabled(false);
    m_forwardAction->setEnabled(false);
202

203
    m_playTimer = new QTimer(this);
204
    connect(m_playTimer, SIGNAL(timeout()), this, SLOT(slotPollPlay()));
205

206
    if(m_sliderAction && m_sliderAction->getTrackPositionSlider() && m_sliderAction->getVolumeSlider()) {
207 208 209
        connect(m_sliderAction->getTrackPositionSlider(), SIGNAL(valueChanged(int)), this, SLOT(slotTrackPositionSliderUpdate(int)));
        connect(m_sliderAction->getTrackPositionSlider(), SIGNAL(sliderPressed()), this, SLOT(slotTrackPositionSliderClicked()));
        connect(m_sliderAction->getTrackPositionSlider(), SIGNAL(sliderReleased()), this, SLOT(slotTrackPositionSliderReleased()));
210
        m_sliderAction->getTrackPositionSlider()->setEnabled(false);
211

212
        connect(m_sliderAction->getVolumeSlider(), SIGNAL(valueChanged(int)), this, SLOT(slotSetVolume(int)));
213
    }
214 215
    
    int playerType = 0;
216 217
    if(m_outputSelectAction) {
	playerType = m_outputSelectAction->currentItem();
218
	connect(m_outputSelectAction, SIGNAL(activated(int)), this, SLOT(slotSetOutput(int)));
219 220
    }

221
    m_player = Player::createPlayer(playerType);
222 223
}

224

225 226 227 228 229 230 231 232
void JuK::processArgs()
{
    KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
    QStringList files;
    
    for(int i = 0; i < args->count(); i++)
	files.append(args->arg(i));

233
    m_splitter->open(files);
234 235
}

236 237 238 239 240 241 242 243 244
/**
 * These are settings that need to be know before setting up the GUI.
 */

void JuK::readSettings()
{
    KConfig *config = KGlobal::config();
    { // general settings
        KConfigGroupSaver saver(config, "Settings");
245
	m_restore = config->readBoolEntry("RestoreOnLoad", true);
246 247 248
    }
}

249 250
void JuK::readConfig()
{
251
    // Automagically save and m_restore many window settings.
252 253
    setAutoSaveSettings();

254
    KConfig *config = KGlobal::config();
255
    { // m_player settings
256
        KConfigGroupSaver saver(config, "Player");
257 258 259
        if(m_sliderAction && m_sliderAction->getVolumeSlider()) {
            int volume = config->readNumEntry("Volume", m_sliderAction->getVolumeSlider()->maxValue());
            m_sliderAction->getVolumeSlider()->setValue(volume);
260
        }
261
	if(m_randomPlayAction) {
262
	    bool randomPlay = config->readBoolEntry("RandomPlay", false);
263
	    m_randomPlayAction->setChecked(randomPlay);
264
	}
265
    }
266
    { // view settings
267
        KConfigGroupSaver saver(config, "View");
268
	bool showEditor = config->readBoolEntry("ShowEditor", false);
269 270
	m_showEditorAction->setChecked(showEditor);
	m_splitter->slotSetEditorVisible(showEditor);
271
    }
272 273
    { // general settings
        KConfigGroupSaver saver(config, "Settings");
274
	bool dockInSystemTray = config->readBoolEntry("DockInSystemTray", true);
275
	m_toggleSystemTrayAction->setChecked(dockInSystemTray);
276
	
277 278
	if(m_outputSelectAction)
	    m_outputSelectAction->setCurrentItem(config->readNumEntry("MediaSystem", 0));
279
	
280
    }
281

282 283
    if(m_restoreOnLoadAction)
	m_restoreOnLoadAction->setChecked(m_restore);
284
    
285 286 287 288
}

void JuK::saveConfig()
{
289
    KConfig *config = KGlobal::config();
290
    { // m_player settings
291
        KConfigGroupSaver saver(config, "Player");
292 293 294 295
        if(m_sliderAction && m_sliderAction->getVolumeSlider())
            config->writeEntry("Volume", m_sliderAction->getVolumeSlider()->value());
	if(m_randomPlayAction)
	    config->writeEntry("RandomPlay", m_randomPlayAction->isChecked());
296
    }
297 298
    { // view settings
        KConfigGroupSaver saver(config, "View");
299
	config->writeEntry("ShowEditor", m_showEditorAction->isChecked());
300
    }
301 302
    { // general settings
        KConfigGroupSaver saver(config, "Settings");
303 304 305 306 307 308
	if(m_restoreOnLoadAction)
	    config->writeEntry("RestoreOnLoad", m_restoreOnLoadAction->isChecked());
	if(m_toggleSystemTrayAction)
	    config->writeEntry("DockInSystemTray", m_toggleSystemTrayAction->isChecked());
	if(m_outputSelectAction)
	    config->writeEntry("MediaSystem", m_outputSelectAction->currentItem());
309
    }
310 311
}

312
bool JuK::queryExit()
313
{
314
    slotStop();
315
    delete m_player;
316
    Cache::instance()->save();
317
    saveConfig();
318
    delete m_splitter;
319
    return true;
320 321
}

322 323 324 325 326 327 328 329 330 331 332 333 334 335
bool JuK::queryClose()
{
    if(m_systemTray) {
	KMessageBox::information(this,
				 i18n("<qt>Closing the main window will keep JuK running in the system tray. "
				      "Use Quit from the File menu to quit the application.</qt>"), 
				 i18n("Docking in System Tray"), "hideOnCloseInfo");
	hide();
	return false;
    }
    else
	return true;
}

336 337 338 339 340 341 342 343 344 345 346 347 348
void JuK::invokeEditSlot( const char *slotName, const char *slot )
{
    QObject *object = focusWidget();
    
    if(!object || !slotName || !slot)
	return;
    
    QMetaObject *meta = object->metaObject();
    QStrList l = meta->slotNames(true);
  
    if(l.find(slotName) == -1)
	return;
    
349 350 351
    connect(this, SIGNAL(signalEdit()), object, slot);
    emit signalEdit();
    disconnect(this, SIGNAL(signalEdit()), object, slot);
352 353
}

354 355 356 357
QString JuK::playingString() const
{
    QString s;
    
358 359
    if(m_splitter->playingArtist().isEmpty())
	s = m_splitter->playingTrack().simplifyWhiteSpace();
360
    else
361
	s = m_splitter->playingArtist().simplifyWhiteSpace() + " - " + m_splitter->playingTrack().simplifyWhiteSpace();
362 363 364 365

    return s;
}

366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
void JuK::updatePlaylistInfo()
{
    m_statusLabel->setPlaylistInfo(m_splitter->selectedPlaylistName(), m_splitter->selectedPlaylistCount());
}

void JuK::play(const QString &file)
{
    if(!m_player)
	return;

    float volume = float(m_sliderAction->getVolumeSlider()->value()) / float(m_sliderAction->getVolumeSlider()->maxValue());

    if(m_player->paused())
	m_player->stop();
    
    m_player->play(file, volume);

    // Make sure that the m_player actually starts before doing anything.

    if(m_player->playing()) {
	m_pauseAction->setEnabled(true);
	m_stopAction->setEnabled(true);
	
	m_backAction->setEnabled(true);
	m_forwardAction->setEnabled(true);
	
	m_sliderAction->getTrackPositionSlider()->setValue(0);
	m_sliderAction->getTrackPositionSlider()->setEnabled(true);
	m_playTimer->start(m_pollInterval);

	m_statusLabel->setPlayingItemInfo(playingString(), m_splitter->playingList());

	emit signalNewSong(playingString());

	if(m_systemTray)
	    m_systemTray->slotPlay();
    }
    else
	slotStop();
}

407 408 409 410
////////////////////////////////////////////////////////////////////////////////
// private slot definitions
////////////////////////////////////////////////////////////////////////////////

411
void JuK::slotPlaylistChanged()
412
{
413 414 415 416 417
    if(m_splitter->collectionListSelected()) {
	m_savePlaylistAction->setEnabled(false);
	m_saveAsPlaylistAction->setEnabled(false);
	m_renamePlaylistAction->setEnabled(false);
	m_deleteItemPlaylistAction->setEnabled(false);	
418 419
    }
    else {
420 421 422 423
	m_savePlaylistAction->setEnabled(true);
	m_saveAsPlaylistAction->setEnabled(true);
	m_renamePlaylistAction->setEnabled(true);
	m_deleteItemPlaylistAction->setEnabled(true);
424
    }
425 426 427 428

    updatePlaylistInfo();
}

429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
////////////////////////////////////////////////////////////////////////////////
// edit menu
////////////////////////////////////////////////////////////////////////////////

void JuK::cut()
{
    invokeEditSlot("cut()", SLOT(cut()));
}

void JuK::copy()
{
    invokeEditSlot("copy()", SLOT(copy()));
}

void JuK::paste()
{
    invokeEditSlot("paste()", SLOT(paste()));
}

void JuK::clear()
{
    invokeEditSlot("clear()", SLOT(clear()));
}

void JuK::selectAll()
{
    invokeEditSlot("selectAll()", SLOT(selectAll()));
}

458
////////////////////////////////////////////////////////////////////////////////
459
// player menu
460 461
////////////////////////////////////////////////////////////////////////////////

462
void JuK::slotPlay()
463
{
464
    if(!m_player)
465 466
	return;

467 468
    if(m_player->paused()) {
        m_player->play();
469

470
	// Here, before doing anything, we want to make sure that the m_player did
471 472
	// in fact start.

473 474 475
        if(m_player->playing()) {
            m_pauseAction->setEnabled(true);
            m_stopAction->setEnabled(true);
476
            m_playTimer->start(m_pollInterval);
477 478
	    if(m_systemTray)
		m_systemTray->slotPlay();
479 480
        }
    }
481 482
    else if(m_player->playing())
	m_player->seekPosition(0);
483
    else
484
	play(m_splitter->playNextFile(m_randomPlayAction->isChecked()));
485 486
}

487
void JuK::slotPause()
488
{
489
    if(!m_player)
490 491
	return;

492 493 494 495 496
    m_playTimer->stop();
    m_player->pause();
    m_pauseAction->setEnabled(false);
    if(m_systemTray)
	m_systemTray->slotPause();
497 498
}

499
void JuK::slotStop()
500
{
501
    if(!m_player)
502 503
	return;

504 505
    m_playTimer->stop();
    m_player->stop();
506

507 508 509 510
    m_pauseAction->setEnabled(false);
    m_stopAction->setEnabled(false);
    m_backAction->setEnabled(false);
    m_forwardAction->setEnabled(false);
511

512 513
    m_sliderAction->getTrackPositionSlider()->setValue(0);
    m_sliderAction->getTrackPositionSlider()->setEnabled(false);
514

515
    m_splitter->stop();
516

517
    m_statusLabel->clear();
518
    
519 520
    if(m_systemTray)
	m_systemTray->slotStop();
521 522
}

523
void JuK::slotBack()
524
{
525
    play(m_splitter->playPreviousFile(m_randomPlayAction->isChecked()));
526 527
}

528
void JuK::slotForward()
529
{
530
    play(m_splitter->playNextFile(m_randomPlayAction->isChecked()));
531 532
}

533 534 535 536
////////////////////////////////////////////////////////////////////////////////
// settings menu
////////////////////////////////////////////////////////////////////////////////

537
void JuK::slotShowGenreListEditor()
538 539 540 541
{
    GenreListEditor * editor = new GenreListEditor();
    editor->exec();
}
542

543
void JuK::slotToggleSystemTray(bool enabled)
544
{
545
    if(enabled && !m_systemTray)
546
	setupSystemTray();
547 548 549
    else if(!enabled && m_systemTray) {
	delete m_systemTray;
	m_systemTray = 0;
550 551 552
    }
}

553
void JuK::slotSetOutput(int output)
554
{
555
    slotStop();
556 557
    delete m_player;
    m_player = Player::createPlayer(output);
558 559
}

560
////////////////////////////////////////////////////////////////////////////////
561
// additional player slots
562 563
////////////////////////////////////////////////////////////////////////////////

564
void JuK::slotTrackPositionSliderClicked()
565
{
566
    m_trackPositionDragging = true;
567 568
}

569
void JuK::slotTrackPositionSliderReleased()
570
{
571
    if(!m_player)
572 573
	return;

574 575
    m_trackPositionDragging = false;
    m_player->seekPosition(m_sliderAction->getTrackPositionSlider()->value());
576 577
}

578
void JuK::slotTrackPositionSliderUpdate(int position)
579
{
580
    if(!m_player)
581 582
	return;

583 584
    if(m_player->playing() && !m_trackPositionDragging && !m_noSeek)
        m_player->seekPosition(position);
585 586
}

587 588
// This method is called when the play timer has expired.

589
void JuK::slotPollPlay()
590
{
591
    if(!m_player)
592 593
	return;

594 595
    // Our locking mechanism.  Since this method adjusts the play slider, we 
    // want to make sure that our adjustments
596
    m_noSeek = true;
597

598
    if(!m_player->playing()) {
599

600
        m_playTimer->stop();
601

602 603
	if(!m_player->paused())
	    play(m_splitter->playNextFile(m_randomPlayAction->isChecked()));
604

605
    }
606 607 608 609
    else if(!m_trackPositionDragging) {
        m_sliderAction->getTrackPositionSlider()->setValue(m_player->position());
	m_statusLabel->setItemTotalTime(m_player->totalTime());
	m_statusLabel->setItemCurrentTime(m_player->currentTime());
610 611 612 613 614 615
    }

    // Ok, this is weird stuff, but it works pretty well.  Ordinarily we don't
    // need to check up on our playing time very often, but in the span of the 
    // last interval, we want to check a lot -- to figure out that we've hit the
    // end of the song as soon as possible.
616

617
    if(m_player->playing() && m_player->totalTime() > 0 && float(m_player->totalTime() - m_player->currentTime()) < m_pollInterval * 2)
618
        m_playTimer->changeInterval(50);
619

620
    m_noSeek = false;
621 622
}

623
void JuK::slotSetVolume(int volume)
624
{
625 626 627
    if(m_player && m_sliderAction && m_sliderAction->getVolumeSlider() &&
       m_sliderAction->getVolumeSlider()->maxValue() > 0 &&
       volume >= 0 && m_sliderAction->getVolumeSlider()->maxValue() >= volume)
628
    {
629
        m_player->setVolume(float(volume) / float(m_sliderAction->getVolumeSlider()->maxValue()));
630 631
    }
}
632

633
#include "juk.moc"