/***************************************************************************
                          kbswitchapp.cpp  -  description
                             -------------------
    begin                : Sun Jul 1 2001
    copyright            : (C) 2001 by Leonid Zeitlin
    email                : lz@europe.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "config.h" 
#include "kbswitchapp.h"
#include "kbconfigdlg.h"
#include "singlewindowwatcher.h"
#include "windowclasswatcher.h"

#include <kdebug.h>
#include <tdelocale.h>
#include <twinmodule.h>

KBSwitchApp::KBSwitchApp()
{
#ifndef HAVE_LIBXKLAVIER
  //m_twin_module = NULL;
  m_watcher = NULL;
#endif	
	if (!m_xkb.xkbAvailable()) return; // oops! No XKB in the server
  m_kbconf.load(config());
  m_cur_groupno = m_next_groupno = -1;
#ifdef HAVE_LIBXKLAVIER
  XklSetGroupPerApp(m_kbconf.group_scope() != KBConfig::SCOPE_GLOBAL);
  if (m_kbconf.toggle_mode())
    XklSetSecondaryGroupsMask(12); /* binary 1100 */
#else
  //resetWindowMap();
  //enableKWinModule();
  enableWatcher();
#endif

  m_intf = new KBSwitchIntf(this, &m_kbconf);
  TQObject::connect(m_intf, TQ_SIGNAL(nextGroupSelected()), this, TQ_SLOT(slotSelectNextGroup()));
  TQObject::connect(m_intf, TQ_SIGNAL(groupSelected(int)), this, TQ_SLOT(slotGroupSelected(int)));

  TQObject::connect(&m_xkb, TQ_SIGNAL(layoutChanged()), this, TQ_SLOT(reconfigure()));
  TQObject::connect(&m_xkb, TQ_SIGNAL(groupChanged(int)), this, TQ_SLOT(slotXkbGroupChanged(int)));

  m_force_group_setting = false;
  int start_group = m_kbconf.default_groupno();

  m_trayicon = new KBSwitchTrayIcon(&m_kbconf);
  TQObject::connect(m_trayicon, TQ_SIGNAL(groupSelected(int)), this,
    TQ_SLOT(slotGroupSelected(int)));
  TQObject::connect(m_trayicon, TQ_SIGNAL(clicked()), this, TQ_SLOT(slotSelectNextGroup()));
  TQObject::connect(m_trayicon, TQ_SIGNAL(preferencesSelected()), this,
    TQ_SLOT(slotPreferences()));
  
  if (start_group != m_xkb.getGroupNo()) {
    setStartGroup(start_group);
  }
  else {
    adaptToGroup(start_group);
  }

  setMainWidget(m_trayicon);
  m_trayicon->show();

#ifdef HAVE_LIBXKLAVIER  
  XklStartListen();
#endif
}

KBSwitchApp::~KBSwitchApp(){
#ifndef HAVE_LIBXKLAVIER  	
  //disableKWinModule();
  disableWatcher();
#endif
}

/** No descriptions */
bool KBSwitchApp::x11EventFilter(XEvent *e){
  // let m_xkb process the event and emit signals if necessary
  m_xkb.processEvent(e);
  return TDEApplication::x11EventFilter(e);
}

/** Update the tray icon to show the flag corresponding to the current keyboard group */
void KBSwitchApp::updateIcon(int groupno){
  if (groupno >= 0 && groupno < m_kbconf.groupCount()) { // check just in case
    m_trayicon->updateTrayIcon(groupno);
    if (m_kbconf.toggle_mode()) m_trayicon->setToggleGroups(m_cur_groupno, m_next_groupno);
  }
}

/** No descriptions */
void KBSwitchApp::slotGroupSelected(int groupno) 
{
#ifdef HAVE_LIBXKLAVIER
  XklAllowOneSwitchToSecondaryGroup();
  m_xkb.setGroupNo(groupno);
#else
  if (m_cur_groupno != groupno) {
    setGroups(groupno, m_cur_groupno);
  }
#endif  
}

/** No descriptions */
void KBSwitchApp::internalToggleGroups()
{
  int tmp = m_next_groupno;
  m_next_groupno = m_cur_groupno;
  m_cur_groupno = tmp;
}

/** No descriptions */
void KBSwitchApp::forceSetGroup(int groupno)
{
  m_force_group_setting = true;
#ifndef HAVE_LIBXKLAVIER  
  //if (m_active_window != m_window_map.end())
  //  m_active_window.data().groupno = groupno;
#endif    
  m_xkb.setGroupNo(groupno);
}

/** Select the next keyboard layout (wraps around) */
void KBSwitchApp::slotSelectNextGroup()
{
#ifdef HAVE_LIBXKLAVIER
  m_xkb.setGroupNo(XklGetNextGroup());
#else
  //forceSetGroup(m_next_groupno);
  m_xkb.setGroupNo(m_next_groupno);
#endif
}

/** Select the previous keyboard layout (wraps around) */
void KBSwitchApp::slotSelectPrevGroup()
{
#ifdef HAVE_LIBXKLAVIER
  m_xkb.setGroupNo(XklGetPrevGroup());
#else
  m_xkb.setGroupNo(m_kbconf.getPrevGroup(m_cur_groupno));
#endif
}

/** No descriptions */
void KBSwitchApp::slotPreferences(){
  KBConfigDlg dlg(&m_kbconf);
  TQObject::connect(&dlg, TQ_SIGNAL(okClicked()), m_trayicon, TQ_SLOT(slotUpdateIcons()));
  TQObject::connect(&dlg, TQ_SIGNAL(applyClicked()), m_trayicon, TQ_SLOT(slotUpdateIcons()));
  TQObject::connect(&dlg, TQ_SIGNAL(okClicked()), this, TQ_SLOT(slotPrefChanged()));
  TQObject::connect(&dlg, TQ_SIGNAL(applyClicked()), this, TQ_SLOT(slotPrefChanged()));
  dlg.exec();
}

/** Called when XKeyboard configuration changes */
void KBSwitchApp::reconfigure(){
  m_kbconf.load(config());
  m_trayicon->reconfigure();

  // make sure default group is still valid
  if (m_kbconf.default_groupno() >= m_xkb.getNumKbdGroups())
    m_kbconf.set_default_groupno(0);
    
  int groupno = m_xkb.getGroupNo();
  if (groupno >= m_xkb.getNumKbdGroups()) {
    // current group no longer valid, reset to default group
    setStartGroup(m_kbconf.default_groupno());
  }
  else if (!m_force_group_setting) {
    adaptToGroup(groupno);
  }
#ifndef HAVE_LIBXKLAVIER  
  //resetWindowMap();
  if (m_watcher) m_watcher->reset();
#endif  
}

/** Respond to XKB changing the current group */
void KBSwitchApp::slotXkbGroupChanged(int groupno){
#ifdef HAVE_LIBXKLAVIER
  updateIcon(groupno);
/*  XWindowAttributes attrs;
  XGetWindowAttributes(tqt_xdisplay(), tqt_xrootwin(), &attrs);
  kdDebug() << "root event mask is " << attrs.your_event_mask << endl;
  kdDebug() << "SubstructureNotifyMask is " <<
   ((attrs.your_event_mask & SubstructureNotifyMask) ? "ON" : "OFF") << endl;*/
#else
  bool accept = false;
  if (m_force_group_setting) { // the change of group is forced by us
    if (groupno == m_cur_groupno) { // the group is what we wanted, fine
      m_force_group_setting = false;
      accept = true;
    }
    else { // oops, not the group we expected
      forceSetGroup(m_cur_groupno); 
    }
  }
  else { // the change is caused by something external, we didn't request it
    if (m_kbconf.toggle_mode() && groupno != m_next_groupno) { // toggle mode, and the group is not correct
      m_xkb.setGroupNo(m_next_groupno);
    }
    else { // adjust to this group
      if (m_kbconf.toggle_mode()) internalToggleGroups();
      else {
        m_cur_groupno = groupno;
        m_next_groupno = m_kbconf.getNextGroup(groupno);
      }
      accept = true;
    }
  }
  if (accept) {
    updateIcon(groupno);
    if (m_watcher) m_watcher->changeGroup(groupno, m_next_groupno);
  }
  /*if (m_kbconf.toggle_mode() && !force_group_setting && groupno != m_next_groupno) {
    forceSetGroup(m_next_groupno);
  }
  else {
    updateIcon(groupno);
    force_group_setting = false;
    if (m_kbconf.toggle_mode()) internalToggleGroups();
    else {
      kdDebug() << "slotXkbGroupChanged, change group to " << groupno << endl;
      m_cur_groupno = groupno;
      m_next_groupno = m_kbconf.getNextGroup(groupno);
    }
    if (m_watcher) m_watcher->changeGroup(groupno, m_next_groupno);    
  }*/
#endif  
}

/** Set the keyboard to the given group and set internal variable accordingly */
void KBSwitchApp::setStartGroup(int start_group){
  /*m_next_groupno = start_group;
  if (m_kbconf.toggle_mode()) {
    m_cur_groupno = getNextGroup(start_group);
  }
  else {
    m_cur_groupno = start_group;
  }
  forceSetGroup(m_next_groupno);*/
  setGroups(start_group, m_kbconf.getNextGroup(start_group));
}

/** adapt internal state to the given group */
void KBSwitchApp::adaptToGroup(int groupno) {
  if (! m_kbconf.toggle_mode() || // if not in toggle mode
      (m_cur_groupno != groupno    // or in toggle mode and internal variables are invalid
      || m_next_groupno >= m_kbconf.groupCount()
      || m_next_groupno == m_cur_groupno)) {
      m_cur_groupno = groupno;
      m_next_groupno = m_kbconf.getNextGroup(groupno);
  }
#ifndef HAVE_LIBXKLAVIER  
  if (m_watcher) m_watcher->changeGroup(groupno, m_next_groupno);
  /*if (m_active_window != m_window_map.end()) {
    m_active_window.data().groupno = groupno;
    m_active_window.data().next_groupno = m_next_groupno;
  }*/
#endif  
  updateIcon(groupno);
}

/*void KBSwitchApp::slotWindowChanged(WId activeWindow)
{
#ifndef HAVE_LIBXKLAVIER
  m_active_window = m_window_map.find(activeWindow);
  if (m_active_window == m_window_map.end())
    addWindowToMap(activeWindow);
  if (m_active_window.data().groupno != m_cur_groupno) {
    setGroups(m_active_window.data().groupno, m_active_window.data().next_groupno);
  }
  else m_next_groupno = m_active_window.data().next_groupno;
#endif  
}*/

void KBSwitchApp::slotWindowChanged(int groupno, int next_groupno) 
{
  if (groupno != m_cur_groupno) {
    setGroups(groupno, next_groupno);
  }  
  else m_next_groupno = next_groupno;
}

/*void KBSwitchApp::slotWindowRemoved(WId window)
{
#ifndef HAVE_LIBXKLAVIER
  m_window_map.remove(window);
#endif  
}

#ifndef HAVE_LIBXKLAVIER
void KBSwitchApp::resetWindowMap()
{
  WId active_window_id;

  m_window_map.clear();
  
  if (m_twin_module && (active_window_id = m_twin_module->activeWindow())) 
    addWindowToMap(active_window_id);
  else
    m_active_window = m_window_map.end();
}*/

/** Enable window manager notifications */
/*void KBSwitchApp::enableKWinModule()
{
  if (m_twin_module == NULL) {
    m_twin_module = new KWinModule();
    connect(m_twin_module, TQ_SIGNAL(activeWindowChanged(WId)), TQ_SLOT(slotWindowChanged(WId)));
    connect(m_twin_module, TQ_SIGNAL(windowRemoved(WId)), TQ_SLOT(slotWindowRemoved(WId)));
    resetWindowMap();
    if (m_cur_groupno != -1 && m_cur_groupno != m_kbconf.default_groupno())
      setStartGroup(m_kbconf.default_groupno());
  }
}*/

/** Disable window manager notifications */
/*void KBSwitchApp::disableKWinModule()
{
  if (m_twin_module) {
    m_twin_module->disconnect();
    delete m_twin_module;
    m_twin_module = NULL;
    resetWindowMap();
  }
}*/

/** adds a new window to the internal window map */
/*void KBSwitchApp::addWindowToMap(WId window_id)
{
  KBWinInfo wininfo = { m_kbconf.default_groupno(),
    m_kbconf.getNextGroup(m_kbconf.default_groupno()) };

  m_active_window = m_window_map.insert(window_id, wininfo);
}
#endif*/

void KBSwitchApp::enableWatcher()
{
  if (!m_watcher) {
    m_watcher_type = m_kbconf.group_scope();
    if (m_watcher_type == KBConfig::SCOPE_WINDOW)
      m_watcher = new SingleWindowWatcher(&m_kbconf, this);
    else if (m_watcher_type == KBConfig::SCOPE_CLASS)
      m_watcher = new WindowClassWatcher(&m_kbconf, this);
    else return; // if scope is global, don't create watcher
    connect(m_watcher, TQ_SIGNAL(windowChanged(int, int )), 
      TQ_SLOT(slotWindowChanged(int, int)));
  }     
}

void KBSwitchApp::disableWatcher()
{
  if (m_watcher) {
    m_watcher->disconnect();
    delete m_watcher;
    m_watcher = NULL;
  }  
}

/** React to a change in KKBSwitch's user preferences,
  * made by user in Configure dialog */
void KBSwitchApp::slotPrefChanged()
{
#ifndef HAVE_LIBXKLAVIER	
  if (m_kbconf.group_scope() != m_watcher_type) {
    disableWatcher();
    enableWatcher();
  }  
#endif    
}

/** Set the current and next groups */
void KBSwitchApp::setGroups(int group, int next_group)
{
  m_cur_groupno = group;
  m_next_groupno = next_group;
  forceSetGroup(group);
}

#include "kbswitchapp.moc"
