/*
 * KMix -- KDE's full featured mini mixer
 *
 *
 * Copyright (C) 2000 Stefan Schimanski <1Stein@gmx.de>
 * Copyright (C) 2001 Preston Brown <pbrown@kde.org>
 * Copyright (C) 2003 Sven Leiber <s.leiber@web.de>
 * Copyright (C) 2004 Christian Esken <esken@kde.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <kpanelapplet.h>
#include <kdialog.h>
#include <kaudioplayer.h>
#include <kiconloader.h>
#include <kdebug.h>
#include <khelpmenu.h>
#include <twin.h>
#include <tdeaction.h>
#include <tdeapplication.h>
#include <tdelocale.h>
#include <tdepopupmenu.h>
#include <tdeglobalsettings.h>
#include <tdestandarddirs.h>
#include <tdemainwindow.h>

#include <tqapplication.h>
#include <tqcursor.h>
#include <tqtooltip.h>
#include <tqimage.h>
#include <X11/Xlib.h>
#include <fixx11h.h>

#include "dialogselectmaster.h"
#include "mixer.h"
#include "mixdevicewidget.h"
#include "kmixdockwidget.h"
#include "kmixsettings.h"
#include "viewdockareapopup.h"

KMixDockWidget::KMixDockWidget( Mixer *mixer, TQWidget *parent, const char *name, bool volumePopup, bool dockIconMuting )
    : KSystemTray( parent, name ),
      m_mixer(mixer),
      _dockAreaPopup(0L),
      _audioPlayer(0L),
      _playBeepOnVolumeChange(false), // disabled due to triggering a "Bug"
      _oldToolTipValue(-1),
      _oldPixmapType('-'),
      _volumePopup(volumePopup),
      _dockIconMuting(dockIconMuting),
      _dsm(NULL)
{
    Mixer* preferredMasterMixer = Mixer::masterCard();
    if ( preferredMasterMixer != 0 ) {
       m_mixer = preferredMasterMixer;
    }
    MixDevice* mdMaster = Mixer::masterCardDevice();
    if ( mdMaster != 0 ) {
       m_mixer->setMasterDevice(mdMaster->getPK()); //  !! using both Mixer::masterCard() and m_mixer->masterDevice() is nonsense !!
    }
    createActions();
    createMasterVolWidget();
    connect(this, TQ_SIGNAL(quitSelected()), tdeApp, TQ_SLOT(quitExtended()));

    TDEGlobal::dirs()->addResourceDir("icons_crystal",    locate("appdata", "pics/crystal/"));
    TDEGlobal::dirs()->addResourceDir("icons_oldcrystal", locate("appdata", "pics/oldcrystal/"));
}

KMixDockWidget::~KMixDockWidget()
{
    if (_dsm)
    {
        delete _dsm;
    }
    delete _audioPlayer;
    delete _dockAreaPopup;
}

void KMixDockWidget::createActions()
{
    TDEPopupMenu *popupMenu = contextMenu();

    // Put "Mute" selector in context menu
    (void)new TDEToggleAction(i18n("M&ute"), 0, this, TQ_SLOT(dockMute()),
                              actionCollection(), "dock_mute");
    TDEAction *a = actionCollection()->action("dock_mute");
    if (a)
    {
        a->plug(popupMenu);
    }

    // Put "Select Master Channel" dialog in context menu
    if (m_mixer)
    {
        (void)new TDEAction(i18n("Select Master Channel..."), 0, this, TQ_SLOT(selectMaster()),
                            actionCollection(), "select_master");
        a = actionCollection()->action("select_master");
        if (a)
        {
            a->plug(popupMenu);
        }
    }
    // Show/hide mixer window (use "minimizeRestore" action
    a = actionCollection()->action("minimizeRestore");
    if (a)
    {
        a->plug(popupMenu);
    }

    popupMenu->insertSeparator();

    // KMix Options
    TDEMainWindow *toplevel = static_cast<TDEMainWindow*>(parent());
    a = toplevel->actionCollection()->action(KStdAction::name(KStdAction::Preferences));

    if (a)
    {
        a->plug(popupMenu);
    }

    // Help and quit
    popupMenu->insertItem(SmallIcon("help"), KStdGuiItem::help().text(), (new KHelpMenu(this, TDEGlobal::instance()->aboutData(), false))->menu(), false);
    popupMenu->insertSeparator();
    a = actionCollection()->action(KStdAction::name(KStdAction::Quit));
    if (a)
    {
        a->plug(popupMenu);
    }

    // Setup volume preview
    if (_playBeepOnVolumeChange)
    {
        _audioPlayer = new KAudioPlayer("KDE_Beep_Digital_1.ogg");
    }
}

void KMixDockWidget::createMasterVolWidget()
{
     // Reset flags, so that the dock icon will be reconstructed
     _oldToolTipValue = -1;
     _oldPixmapType   = '-';

    if (m_mixer == 0) {
        // In case that there is no mixer installed, there will be no newVolumeLevels() signal's
        // Thus we prepare the dock areas manually
        setVolumeTip();
        updatePixmap(false);
        return;
    }
    // create devices
    if (_dockAreaPopup)
    {
      // Delete the old popup widget if we are changing the channel
      deleteMasterVolWidget();
    }

    _dockAreaPopup = new ViewDockAreaPopup(0, "dockArea", m_mixer, 0, this);
    _dockAreaPopup->createDeviceWidgets();
    m_mixer->readSetFromHWforceUpdate();  // after changing the master device, make sure to re-read (otherwise no "changed()" signals might get sent by the Mixer
    /* With the recently introduced TQSocketNotifier stuff, we can't rely on regular timer updates
       any longer. Also the readSetFromHWforceUpdate() won't be enough. As a workaround, we trigger
       all "repaints" manually here.
       The call to m_mixer->readSetFromHWforceUpdate() is most likely superfluous, even if we don't use TQSocketNotifier (e.g. in backends OSS, Solaris, ...)
     */
    setVolumeTip();
    updatePixmap(false);
    /* We are setting up 3 connections:
     * Refreshig the _dockAreaPopup (not anymore neccesary, because ViewBase already does it)
     * Refreshing the Tooltip
     * Refreshing the Icon
     *
     */
    connect(m_mixer, TQ_SIGNAL(newVolumeLevels()), this, TQ_SLOT(setVolumeTip()));
    connect(m_mixer, TQ_SIGNAL(newVolumeLevels()), this, TQ_SLOT(slotUpdatePixmap()));
}

void KMixDockWidget::deleteMasterVolWidget()
{
    if (_dockAreaPopup)
    {
        delete _dockAreaPopup;
        _dockAreaPopup = NULL;
    }
    if (m_mixer)
    {
        disconnect(m_mixer, TQ_SIGNAL(newVolumeLevels()), this, TQ_SLOT(setVolumeTip()));
        disconnect(m_mixer, TQ_SIGNAL(newVolumeLevels()), this, TQ_SLOT(slotUpdatePixmap()));
    }
}

void KMixDockWidget::slotUpdatePixmap()
{
    updatePixmap(false);
}

void KMixDockWidget::selectMaster()
{
   if (!_dsm)
   {
       _dsm = new DialogSelectMaster(m_mixer);
       connect(_dsm, TQ_SIGNAL(newMasterSelected(bool, int, const TQString&)), TQ_SLOT( handleNewMaster(bool, int, const TQString&)));
   }
   _dsm->show(m_mixer);
}


void KMixDockWidget::handleNewMaster(bool defaultMaster, int soundcard_id, const TQString &channel_id)
{
  //kdDebug(67100) << "KMixDockWidget::handleNewMaster() default master=" << defaultMaster << ", soundcard_id=" << soundcard_id << ", channel_id=" << channel_id << endl;
  tdeApp->config()->setGroup(0);
  tdeApp->config()->writeEntry("UseDefaultMaster", defaultMaster);
  Mixer *mixer;
  TQString channel = TQString::null;
  if (defaultMaster)
  {
    mixer = Mixer::mixers().first();
    if (mixer)
    {
        MixSet ms = mixer->getMixSet();
        for (MixDevice *md = ms.first(); md != 0; md = ms.next())
        {
            if (!md->isRecordable() && !md->isSwitch() && !md->isEnum())
            {
                channel = md->getPK();
                break;
            }
        }
    }
  }
  else
  {
    mixer = Mixer::mixers().at(soundcard_id);
    channel = channel_id;
  }

  if (!mixer || channel.isEmpty()) {
    kdError(67100) << "KMixDockWidget::createPage(): Invalid Mixer (default master=" << defaultMaster << ", soundcard_id="
                   << soundcard_id << ", channel_id=" << channel_id << ")" << endl;
    return; // can not happen
  }
  deleteMasterVolWidget();
  m_mixer = mixer;
  Mixer::setMasterCard(mixer->id()); // We must save this information "somewhere".
  Mixer::setMasterCardDevice(channel);
  createMasterVolWidget();
}


long
KMixDockWidget::getAvgVolume()
{
    MixDevice *md = 0;
    if ( _dockAreaPopup != 0 ) {
        md = _dockAreaPopup->dockDevice();
    }

    if ( md == 0 || md->maxVolume() == 0 )
        return -1;

    return (md->getVolume().getAvgVolume(Volume::MMAIN)*100 )/( md->maxVolume() );
}

void
KMixDockWidget::setVolumeTip()
{
    MixDevice *md = 0;
    if ( _dockAreaPopup != 0 ) {
        md = _dockAreaPopup->dockDevice();
    }

    TQString tip = "";

    int newToolTipValue = 0;
    if ( md == 0 )
    {
        tip = i18n("Mixer cannot be found"); // !! text could be reworked
	newToolTipValue = -2;
    }
    else
    {
        long val = getAvgVolume();
	newToolTipValue = val + 10000*md->isMuted();
	if ( _oldToolTipValue != newToolTipValue ) {
	    tip = i18n( "Volume at %1%" ).arg( val );
	    if ( md->isMuted() ) {
		tip += i18n( " (Muted)" );
	    }
	}
	// create a new "virtual" value. With that we see "volume changes" as well as "muted changes"
	newToolTipValue = val + 10000*md->isMuted();
    }

    // The actual updating is only done when the "toolTipValue" was changed
    if ( newToolTipValue != _oldToolTipValue ) {
	// changed (or completely new tooltip)
	if ( _oldToolTipValue >= 0 ) {
	    // there was an old Tooltip: remove it
	    TQToolTip::remove(this);
	}
	TQToolTip::add(this, tip);
    }
    _oldToolTipValue = newToolTipValue;
}

void
KMixDockWidget::updatePixmap(bool force)
{
    MixDevice *md = 0;
    if ( _dockAreaPopup != 0 ) {
        md = _dockAreaPopup->dockDevice();
    }
    char newPixmapType;
    if ( md == 0 )
    {
	newPixmapType = 'e';
    }
    else if ( md->isMuted() )
    {
	newPixmapType = 'm';
    }
    else
    {
	long avgVol = getAvgVolume();
	if ( avgVol <= 33 )
		newPixmapType = 'L';
	else if ( avgVol <= 67 )
		newPixmapType = 'M';
	else
		newPixmapType = 'H';
    }

    if (( newPixmapType != _oldPixmapType ) || (force == true)) {
	// Pixmap must be changed => do so
	// Honor Free Desktop specifications that allow for arbitrary system tray icon sizes
	TQPixmap origpixmap;
	TQPixmap scaledpixmap;
	TQImage newIcon;

	TQStringList fallback;
	switch ( newPixmapType ) {
		case 'm': fallback << "audio-volume-muted"  << "kmixdocked_mute"; break;
		case 'L': fallback << "audio-volume-low"    << "kmixdocked";	  break;
		case 'M': fallback << "audio-volume-medium" << "kmixdocked";	  break;
		case 'H': fallback << "audio-volume-high"   << "kmixdocked";	  break;
	}

	TQString icon = getIconPath(fallback);
	if (icon.isNull())
	{
		icon = getIconPath("audio-volume-error");
	}

	origpixmap = isShown() ? loadSizedIcon(icon, width()) : loadIcon(icon);
	newIcon    = origpixmap;
	if (isShown()) {
		newIcon = newIcon.smoothScale(width(), height());
	}
	scaledpixmap = newIcon;
	setPixmap(scaledpixmap);

	_oldPixmapType = newPixmapType;
    }
}

TQString KMixDockWidget::getIconPath(TQStringList fallback)
{
	auto iconTheme = KMixSettings::iconTheme();

	TQCString iconThemeName;
	if (iconTheme != KMixSettings::EnumIconTheme::System)
	{
		switch (iconTheme)
		{
			case KMixSettings::EnumIconTheme::OldCrystal:
				iconThemeName = "oldcrystal";
				break;

			default:
			case KMixSettings::EnumIconTheme::Crystal:
				iconThemeName = "crystal";
				break;
		}
	}

	for (TQStringList::iterator it = fallback.begin(); it != fallback.end(); ++it)
	{
		if (iconTheme == KMixSettings::EnumIconTheme::System)
		{
			TQString iconPath = tdeApp->iconLoader()->iconPath((*it), TDEIcon::Panel, true);
			if (!iconPath.isNull())
			{
				return iconPath;
			}
		}

		else
		{
			TQCString type = "icons_" + iconThemeName;

			TQString iconPath = TDEGlobal::dirs()->findResource(type, TQString("%1.png").arg(*it));
			if (!iconPath.isNull()) return iconPath;

			iconPath = TDEGlobal::dirs()->findResource(type, TQString("%1.svg").arg(*it));
			if (!iconPath.isNull()) return iconPath;
		}
	}
	return TQString::null;
}

void KMixDockWidget::resizeEvent ( TQResizeEvent * )
{
	updatePixmap(true);
}

void KMixDockWidget::showEvent ( TQShowEvent *se )
{
	updatePixmap(true);
}

void
KMixDockWidget::mousePressEvent(TQMouseEvent *me)
{
	if ( _dockAreaPopup == 0 ) {
		return KSystemTray::mousePressEvent(me);
	}

        // esken: Due to overwhelming request, LeftButton shows the ViewDockAreaPopup, if configured
        //        to do so. Otherwise the main window will be shown.
	if ( me->button() == TQt::LeftButton )
	{
		if ( ! _volumePopup ) {
                    // Case 1: User wants to show main window => This is the KSystemTray default action
		    return KSystemTray::mousePressEvent(me);
		}

                // Case 2: User wants to show volume popup
		if ( _dockAreaPopup->justHidden() )
			return;

		if ( _dockAreaPopup->isVisible() )
		{
			_dockAreaPopup->hide();
			return;
		}

		int h = _dockAreaPopup->height();
		int x = this->mapToGlobal( TQPoint( 0, 0 ) ).x() + this->width()/2 - _dockAreaPopup->width()/2;
		int y = this->mapToGlobal( TQPoint( 0, 0 ) ).y() - h;
		if ( y < 0 )
			y = y + h + this->height();

		_dockAreaPopup->move(x, y);  // so that the mouse is outside of the widget

		// Now handle Multihead displays. And also make sure that the dialog is not
		// moved out-of-the screen on the right (see Bug 101742).
		TQDesktopWidget* vdesktop = TQApplication::desktop();
		const TQRect& vScreenSize = vdesktop->screenGeometry(_dockAreaPopup);
		if ( (x+_dockAreaPopup->width()) > (vScreenSize.width() + vScreenSize.x()) ) {
			// move horizontally, so that it is completely visible
			_dockAreaPopup->move(vScreenSize.width() + vScreenSize.x() - _dockAreaPopup->width() -1 , y);
		} // horizontally out-of bound
		else if ( x < vScreenSize.x() ) {
			_dockAreaPopup->move(vScreenSize.x(), y);
		}
		// the above stuff could also be implemented vertically

		_dockAreaPopup->show();
		KWin::setState(_dockAreaPopup->winId(), NET::StaysOnTop | NET::SkipTaskbar | NET::SkipPager );

		TQWidget::mousePressEvent(me); // KSystemTray's shouldn't do the default action for this
		return;
	} // LeftMouseButton pressed
	else if ( me->button() ==  TQt::MidButton ) {
		if ( ! _dockIconMuting ) {
            toggleActive();
        } else {
            dockMute();
        }
		return;
	}
	else {
		KSystemTray::mousePressEvent(me);
	} // Other MouseButton pressed

}

void
KMixDockWidget::mouseReleaseEvent( TQMouseEvent *me )
{

    KSystemTray::mouseReleaseEvent(me);
}

void
KMixDockWidget::wheelEvent(TQWheelEvent *e)
{
  MixDevice *md = 0;
  if ( _dockAreaPopup != 0 ) {
      md = _dockAreaPopup->dockDevice();
  }
  if ( md != 0 )
  {
    Volume vol = md->getVolume();
    int inc = vol.maxVolume() / 20;

    if ( inc == 0 ) inc = 1;

    for ( int i = 0; i < vol.count(); i++ ) {
        int newVal = vol[i] + (inc * (e->delta() / 120));
        if( newVal < 0 ) newVal = 0;
        vol.setVolume( (Volume::ChannelID)i, newVal < vol.maxVolume() ? newVal : vol.maxVolume() );
    }

    if ( _playBeepOnVolumeChange ) {
        _audioPlayer->play();
    }
    md->getVolume().setVolume(vol);
    m_mixer->commitVolumeChange(md);
    // refresh the toolTip (TQt removes it on a MouseWheel event)
    // Mhhh, it doesn't work. TQt does not show it again.
    setVolumeTip();
    // Simulate a mouse move to make TQt show the tooltip again
    TQApplication::postEvent( this, new TQMouseEvent( TQEvent::MouseMove, TQCursor::pos(), TQt::NoButton, TQt::NoButton ) );

  }
}

void
KMixDockWidget::dockMute()
{
	MixDevice *md = 0;
	if ( _dockAreaPopup != 0 )
	{
		md = _dockAreaPopup->dockDevice();
		if ( md != 0 ) {
        		md->setMuted( !md->isMuted() );
        		m_mixer->commitVolumeChange( md );
        		updatePixmap(false);
		}
	}
}

void
KMixDockWidget::contextMenuAboutToShow( TDEPopupMenu* /* menu */ )
{
    TDEAction* showAction = actionCollection()->action("minimizeRestore");
    if ( parentWidget() && showAction )
    {
        if ( parentWidget()->isVisible() )
        {
            showAction->setText( i18n("Hide Mixer Window") );
        }
        else
        {
            showAction->setText( i18n("Show Mixer Window") );
        }
    }

    // Enable/Disable "Muted" menu item
    MixDevice *md = 0;
    if ( _dockAreaPopup != 0 )
    {
        md = _dockAreaPopup->dockDevice();
        TDEToggleAction *dockMuteAction = static_cast<TDEToggleAction*>(actionCollection()->action("dock_mute"));
	//kdDebug(67100) << "---> md=" << md << "dockMuteAction=" << dockMuteAction << "isMuted=" << md->isMuted() << endl;
        if ( md != 0 && dockMuteAction != 0 ) {
           dockMuteAction->setChecked( md->isMuted() );
        }
    }
}

#include "kmixdockwidget.moc"