/* -*- C++ -*-
   This file implements kabīs toplevel widget.

   the KDE addressbook

   $ Author: Mirko Boehm $
   $ Copyright: (C) 1996-2000, Mirko Boehm $
   $ Contact: mirko@kde.org
         http://www.kde.org $
   $ License: GPL with the following explicit clarification:
         This code may be linked against any version of the Qt toolkit
         from Troll Tech, Norway. $

   $Revision: 1.72 $	
*/

#include "kab_topwidget.h"
#include "kab_kab1importer.h"
#include "kab_mainwidget.h"
#include <kabapi.h>
#include <qconfigDB.h>
#include "look_basic.h"
#include "look_businesscard.h"
#include "look_edit.h"
#include "widget_datanavigator.h"
#include "kab_searchwidget.h"
#include "kab_searchresultswidget.h"
#include <kaction.h>
#include <kmenubar.h>
#include <kmessagebox.h>
#include <klocale.h>
#include <kiconloader.h>
#include <kcombobox.h>
#include <kapp.h>
#include <kstdaccel.h>
#include <kstddirs.h>
#include <kdialogbase.h>
#include <kaboutdialog.h>
#include <knotifyclient.h>
#include <qtimer.h>
#include <qframe.h>
#include <qstringlist.h>
#include <qlayout.h>
#include <qvariant.h>
#include <qregexp.h>
#include <qcheckbox.h>
#include <qradiobutton.h>
// #include <qprinter.h>
#include <qpainter.h>
#include <qpaintdevicemetrics.h>
#include <qfontmetrics.h>
#include <stdio.h>
#include <kdebug.h>
#include <list>

// ----- toolbar descriptors:
const char* MainToolBar="mainToolBar";
const char* EntryNavigation="entryNavigation";

TopLevelWidget::TopLevelWidget()
  : KMainWindow(0),
    current(0),
    mainwidget(0),
    // hasEmailAddresses(false),
    // hasURLs(false),
    timer(new QTimer(this)),
    messages(new QStringList),
    closingdown(false)
{
  kdDebug() << "TopLevelWidget ctor: called." << endl;
  // -----
  Section* section;
  KeyValueMap *keys;
  // -----
  api=new KabAPI(this);
  if(api==0 || timer==0 || messages==0)
    {
      KMessageBox::sorry
	(this, i18n("Out of memory."),
	 i18n("General failure"));
      ::exit(-1);
    }
  // -----
  connect(api, SIGNAL(setStatus(const QString&)),
	  SLOT(setStatus(const QString&)));
  connect(timer, SIGNAL(timeout()), SLOT(statusbarTimeOut()));
  // -----
  if(!initializeInterface())
    {
      KMessageBox::sorry
	(this,
	 i18n("Could not connect to the address database.\n"
	      "The KDE addressbook will not run without it.\n"
	      "The application will be closed."),
	 i18n("Database error"));
      ::exit(-1);
    }
  // ----- make the main widget:
  mainwidget=new KabMainWidget(api, this);
  CHECK_PTR(mainwidget);
  setCentralWidget(mainwidget);
  connect(mainwidget->searchresults(), SIGNAL(showMe(bool)), 
	  SLOT(slotShowSearchResults(bool)));
  connect(mainwidget->searchresults(), SIGNAL(printResults()),
	  SLOT(slotPrintSearchResults()));
  // -----
  makeActions();
  makeMenu();
  makeToolbar();
  makeStatusbar();
  applyMainWindowSettings(kapp->config(), "Main Window");
  connect(this, SIGNAL(databaseChanged()),
	  api->addressbook(), SLOT(externalChange()));
  // -----
  loadConfiguration();
  entriesChanged();
  // ----- now read file configuration saved at end of last session:
  section=api->addressbook()->configurationSection();
  if(section==0)
    { // should not happen, but the user might have modified the file manually
      kdDebug() << "TopLevelWidget ctor: configuration section corrupted."
		<< endl;
    } else {
      QCString lastCurrentKey;
      long lastViewMode=NoView;
      int index;
      KabKey kabkey;
      // -----
      keys=section->getKeys();
      if(rememberLastEntry)
	{
	  if(keys->get("LastCurrentKey", lastCurrentKey))
	    {
	      kdDebug() << "TopLevelWidget ctor: found last current key "
			<< (const char*) lastCurrentKey << "." << endl;
	      kabkey.setKey(lastCurrentKey);
	      if(api->addressbook()->getIndex(kabkey, index)==AddressBook::NoError)
		{
		  kdDebug() << "TopLevelWidget ctor: entry still exists, index is "
			    << index << ", displaying..." << endl;
		  activateEntry(index);
		}
	    }
	} else {
	  if(api->addressbook()->noOfEntries()>0)
	    {
	      activateEntry(0);
	    }
	}
      if(rememberLastView)
	{
	  if(keys->get("LastViewMode", lastViewMode))
	    {
	      kdDebug() << "TopLevelWidget ctor: found last view mode "
			<< lastViewMode << "." << endl;
	
	      if(lastViewMode<=NoView || lastViewMode>=NoOfViews)
		{
		  kdDebug() << "TopLevelWidget ctor: found last view mode "
			    << "is corrupt, ignoring." << endl;
		  slotSelectViewEdit(true); // this initializes the menu, too (default mode)
		  actionSelectViewEdit->setChecked(true);
		} else {
		  switch(lastViewMode)
		    {
		    case BusinessCard:
		      slotSelectViewBC(true);
		      actionSelectViewBC->setChecked(true);
		      break;
		    case Editing:
		      slotSelectViewEdit(true);
		      actionSelectViewEdit->setChecked(true);
		      break;
		    default:
		      slotSelectViewEdit(true);
		      actionSelectViewEdit->setChecked(true);
		    }
		}
	    }
	} else {
	  slotSelectViewEdit(true);
	  actionSelectViewEdit->setChecked(true);
	}
    }
  // -----
  connect(mainwidget, SIGNAL(setStatus(const QString&)), 
	  SLOT(setStatus(const QString&)));
  connect(mainwidget->searchresults(), SIGNAL(entrySelected(int)),
	  SLOT(slotActivateEntry(int)));
  setStatus(i18n("Welcome to kab 2.1"));
  setStatus(i18n("The KDE Addressbook 2.1."));
  setStatus(i18n("Written by Mirko Boehm (mirko@kde.org)."));
  setStatus(i18n("Visit www.kde.org for the latest news."));
  kdDebug() << "TopLevelWidget ctor: done." << endl;
  modified=false;
}

TopLevelWidget::~TopLevelWidget()
{
}

bool TopLevelWidget::cleanup()
{
  // ----- save preferences and settings:
  Section* section;
  KeyValueMap *keys;
  kdDebug() << "TopLevelWidget::cleanup: called." << endl;
  // -----
  if(api!=0) //  should not be ...
    {
      // ----- first save configuration settings for this file:
      section=api->addressbook()->configurationSection();
      if(section==0)
	{ // should not happen, but the user might have modified the file manually
	  kdDebug() << "TopLevelWidget::cleanup: configuration section corrupted. "
		    << "Unable to save state." << endl;
	} else {
	  keys=section->getKeys();
	  if(keys->insert("LastCurrentKey", current->getKey(), true))
	    {
	      kdDebug() << "TopLevelWidget::cleanup: saved last current key "
			<< (const char*) current->getKey() << "." << endl;
	    } else {
	      kdDebug() << "TopLevelWidget::cleanup: unable to store last "
			<< "current key." << endl;
	    } 
	  if(keys->insert("LastViewMode", 
			  (long int) mainwidget->viewType(), true))
	    {
	      kdDebug() << "TopLevelWidget::cleanup: stored last view mode "
			<< unsigned(mainwidget->viewType()) << "." << endl;
	    } else {
	      kdDebug() << "TopLevelWidget::cleanup: unable to store last "
			<< "view mode." << endl;
	    }
	}
      // ----- save modifications:
      save();
      // ----- now shut down the KabAPI connection:
      // delete api;
      // api=0;
    }
  if(current!=0)
    {
      delete current;
      current=0;
    }
  closingdown=true;
  kdDebug() << "TopLevelWidget::cleanup: done." << endl;
  return true;
}

void TopLevelWidget::makeActions()
{
  actionExportHTML=
    new KAction(i18n("&Export to HTML..."), "html",
		0,
		this, SLOT(exportHTML()), this);
  actionFirstEntry=
    new KAction(i18n("&First entry"), "start",
		0,
		this, SLOT(slotFirstEntry()), this);
  actionPreviousEntry=
    new KAction(i18n("&Previous entry"), "back",
		0,
		this, SLOT(slotPreviousEntry()), this);
  actionNextEntry=
    new KAction(i18n("&Next entry"), "forward",
		0,
		this, SLOT(slotNextEntry()), this);
  actionLastEntry=
    new KAction(i18n("&Last entry"), "finish",
		0,
		this, SLOT(slotLastEntry()), this);
  actionNewEntry=
    new KAction(i18n("&Add entry"), "filenew",
		CTRL+Key_A,
		this, SLOT(add()), this);
  actionEditEntry=
    new KAction(i18n("&Edit entry"), "edit",
		CTRL+Key_E,
		this, SLOT(editEntrySlot()), this);
  actionDeleteEntry=
    new KAction(i18n("&Remove entry"), "editdelete",
		CTRL+Key_R,
		this, SLOT(remove()), this);
  actionSendEmail=
    new KAction(i18n("Send an e&mail"), "email",
		CTRL+Key_M,
		this, SLOT(mail()), this);
  actionBrowse=
    new KAction(i18n("&Browse"), "konqueror",
		CTRL+Key_B,
		this, SLOT(browse()), this);
  actionSearchEntries=
    new KAction(i18n("&Search entries"), "find",
		CTRL+Key_F,
		this, SLOT(slotSearchEntries()), this);
  actionShowSearchResults=
    new KToggleAction(i18n("Show search results"), "",
		      0, 0, 0, this);
  connect(actionShowSearchResults, SIGNAL(toggled(bool)),
	  mainwidget, SLOT(slotShowSearchResults(bool)));
  actionShowMainToolbar=
    new KToggleAction(i18n("Show main toolbar"), "",
		      0, 0, 0, this);
  connect(actionShowMainToolbar, SIGNAL(toggled(bool)),
	  SLOT(slotShowMainToolbar(bool)));
  actionShowEntryNavToolbar=
    new KToggleAction(i18n("Show entry navigation toolbar"), "",
		      0, 0, 0, this);
  connect(actionShowEntryNavToolbar, SIGNAL(toggled(bool)),
	  SLOT(slotShowEntryNavToolbar(bool)));
  actionPrintEntry=
    new KAction(i18n("Print entry"), "printer1",
		CTRL+Key_T,
		this, SLOT(slotPrintEntry()), this);
  actionPrintEntries=
    new KAction(i18n("Print entries"), "printer2",
		CTRL+Key_E,
		this, SLOT(slotPrintEntries()), this);
  actionSelectViewEdit=
    new KToggleAction(i18n("&Editing"), "edit",
		      0,
		      0, 0, this);
  actionSelectViewBC=
    new KToggleAction(i18n("&Business Card"), "vcard",
		      0,
		      0, 0, this);
  actionSelectViewEdit->setExclusiveGroup("Views");
  actionSelectViewBC->setExclusiveGroup("Views");
  connect(actionSelectViewEdit, SIGNAL(toggled(bool)),
	  SLOT(slotSelectViewEdit(bool)));
  connect(actionSelectViewEdit, SIGNAL(toggled(bool)),
	  SLOT(slotSelectViewEdit(bool)));
  connect(actionSelectViewBC, SIGNAL(toggled(bool)),
	  SLOT(slotSelectViewBC(bool)));
  actionSave=
    new KAction(i18n("&Save"), "filesave",
		KStdAccel::save(),
		this, SLOT(save()), this);
}

void TopLevelWidget::makeMenu()
{
  // ----- the file menu:
  file=new QPopupMenu;
  CHECK_PTR(file);
  // ----- the import (sub-) menu (more should follow):
  import=new QPopupMenu;
  CHECK_PTR(import);
  import->insertItem(i18n("&KDE 1 address book"), this,
		     SLOT(importKab1Addressbook()));
  // ----- the print sub menu:
  print=new QPopupMenu;
  CHECK_PTR(print);
  actionPrintEntry->plug(print);
  actionPrintEntries->plug(print);
  file->insertItem(i18n("&Create new database..."), this,
		   SLOT(createNew()), KStdAccel::openNew());
  file->insertItem(i18n("&Open..."), this,
		   SLOT(loadDatabaseFile()), KStdAccel::open());
  file->insertItem(i18n("Open default &database"), this,
		   SLOT(loadDefaultDatabase()));
  actionSave->plug(file);
  file->insertSeparator();
  file->insertItem(i18n("&Import"), import);
  file->insertItem(i18n("&Print"), print);
  // actionExportHTML->plug(file); // not finished for KDE 2.1, sorry
  file->insertSeparator();
  file->insertItem(i18n("&Quit"), this, SLOT(quit()), KStdAccel::quit());
  // ----- the edit menu:
  edit=new QPopupMenu;
  actionNewEntry->plug(edit);
  actionEditEntry->plug(edit);
  actionDeleteEntry->plug(edit);
  edit->insertSeparator();
  actionSendEmail->plug(edit);
  actionBrowse->plug(edit);
  edit->insertSeparator();
  actionSearchEntries->plug(edit);
  edit->insertSeparator();
  edit->insertItem(i18n("Enable all &messages"), this,
		   SLOT(enableAllMessages()));
  edit->insertItem(i18n("&Configure this file..."), this,
		   SLOT(configureFile()));
  edit->insertSeparator();
  edit->insertItem(i18n("&Preferences for the KDE address book..."), this,
		   SLOT(configureKab()));
  // ----- the view menu:
  menuview=new QPopupMenu;
  actionSelectViewBC->plug(menuview);
  actionSelectViewEdit->plug(menuview);
  /*
    menuview->insertSeparator();
    idViewBC=menuview->insertItem(i18n("&Business card"), this,
    SLOT(selectViewBC()));
    idViewEdit=menuview->insertItem(i18n("&Editing"), this,
    SLOT(selectViewEdit()));
    menuview->setCheckable(true);
  */
  menuview->insertSeparator();
  actionShowSearchResults->plug(menuview);
  slotSelectViewEdit(true); // default look
  actionSelectViewEdit->setChecked(true);
  menuview->insertSeparator();
  actionShowMainToolbar->plug(menuview);
  actionShowEntryNavToolbar->plug(menuview);
  // -----
  menuBar()->insertItem(i18n("&File"), file);
  menuBar()->insertItem(i18n("&Edit"), edit);
  menuBar()->insertItem(i18n("&View"), menuview);
  menuBar()->insertItem
    (i18n("&Help"), helpMenu(QString::null, false));
}

void TopLevelWidget::makeToolbar()
{
  QStringList dummy;
  // ----- mainToolBar:
  actionNewEntry->plug(toolBar(MainToolBar));
  actionEditEntry->plug(toolBar(MainToolBar));
  actionDeleteEntry->plug(toolBar(MainToolBar));
  toolBar(MainToolBar)->insertLineSeparator();
  actionPrintEntry->plug(toolBar(MainToolBar));
  actionPrintEntries->plug(toolBar(MainToolBar));  
  actionSendEmail->plug(toolBar(MainToolBar));
  actionBrowse->plug(toolBar(MainToolBar));
  actionSearchEntries->plug(toolBar(MainToolBar));
  // ----- entryNavigation
  actionFirstEntry->plug(toolBar(EntryNavigation));
  actionPreviousEntry->plug(toolBar(EntryNavigation));
  // ----- the combo box:
  toolBar(EntryNavigation)->insertCombo(dummy, Entries, false,
			 SIGNAL(activated(int)),
			 this, SLOT(entrySelected(int)));
  toolBar(EntryNavigation)->setItemAutoSized(Entries);
  actionNextEntry->plug(toolBar(EntryNavigation));
  actionLastEntry->plug(toolBar(EntryNavigation));
}

bool TopLevelWidget::activateEntry(int index)
{
  QComboBox *combo=toolBar(EntryNavigation)->getCombo(Entries);
  CHECK_PTR(combo);
  // -----
  if(index>=0 && index<combo->count())
    {
      combo->setCurrentItem(index);
      entrySelected(index);
      return true;
    } else {
      return false;
    }
}

void TopLevelWidget::slotFirstEntry()
{
  QComboBox *combo=toolBar(EntryNavigation)->getCombo(Entries);
  CHECK_PTR(combo);
  // -----
  activateEntry(0);
}

void TopLevelWidget::slotPreviousEntry()
{
  QComboBox *combo=toolBar(EntryNavigation)->getCombo(Entries);
  CHECK_PTR(combo);
  int index;
  // -----
  index=QMAX(0, combo->currentItem()-1);
  activateEntry(index);
}

void TopLevelWidget::slotNextEntry()
{
  QComboBox *combo=toolBar(EntryNavigation)->getCombo(Entries);
  CHECK_PTR(combo);
  int index;
  // -----
  index=QMIN(combo->count()-1, combo->currentItem()+1);
  activateEntry(index);
}

void TopLevelWidget::slotLastEntry()
{
  QComboBox *combo=toolBar(EntryNavigation)->getCombo(Entries);
  CHECK_PTR(combo);
  int index;
  // -----
  index=combo->count()==0 ? 0 : combo->count()-1;
  activateEntry(index);
}

void TopLevelWidget::makeStatusbar()
{
  statusBar()->insertItem("20000/20000", Number);
  statusBar()->insertItem("", Text, 1000, true);
}

bool TopLevelWidget::initializeInterface()
{
  if(api->init()!=AddressBook::NoError)
    {
      KMessageBox::sorry
	(this, i18n("The database interface could not be created.\n"
		    "The KDE addressbook will not run without it.\n"
		    "The application will be closed."),
	 i18n("Database error"));
      return false;
    } else {
      connect(api->addressbook(), SIGNAL(changed()),
	      SLOT(entriesChanged()));
      return true;
    }
}

void TopLevelWidget::entrySelected(int index)
{
  kdDebug() << "TopLevelWidget::entrySelected: called ( "
	    << index << ")" << endl;
  // -----
  AddressBook::Entry entry;
  KabKey key;
  int number;
  QString text;
  // ----- do not do anything after quit() has been called
  if(closingdown) return;
  // ----- store user changes:
  currentIndex=index;
  if(modified) // the current entry has been modified
    { // this should only happen when editing view is selected
      // WORK_TO_DO: respect "Query save on change" configuration setting
      if(current!=0) // make sure non-debug versions also work stable
	{
	  if(KMessageBox::questionYesNo
	     (this,
	      i18n("You changed this entry.\nSave changes?\n"
		   "(Unsaved changes will be lost.)"),
	      i18n("Save changes?"))==KMessageBox::Yes)
	    {
	      AddressBook::Entry temp;
	      mainwidget->getView()->getEntry(temp);
	      modified=false;
	      if(api->addressbook()
		 ->change(*current, temp)!=AddressBook::NoError)
		{
		  emit(setStatus(i18n("Error changing the entry.")));
		} else {
		  save();
		}
	    }
	}
    }
  // -----
  number=api->addressbook()->noOfEntries();
  kdDebug() << "TopLevelWidget::entrySelected: " << number
	    <<" entries." << endl;
  if(number!=0)
    {
      if(api->addressbook()->getKey(index, key)!=AddressBook::NoError)
	{
	  kdDebug() << "TopLevelWidget::entrySelected: no such entry "
		    << index << endl;
	}
      if(api->addressbook()->getEntry(key, entry)!=AddressBook::NoError)
	{
	  kdDebug() << "TopLevelWidget::entrySelected: cannot access"
		    << " existing entry." << endl;
	}
    } else {
      kdDebug() << "TopLevelWidget::entrySelected: no entries." << endl;
    }
  // -----
  if(number==0)
    {
      text=i18n("No entries.");
    } else {
      text=text.sprintf("%i/%i", index+1, number);
    }
  statusBar()->changeItem(text, Number);
  // -----
  mainwidget->getView()->setEntry(entry);
  modified=false; // finally reset modification mark
  if(current!=0)
    { // make sure the old key is deleted:
      delete current; current=0;
    }
  current=new KabKey(key);
  // ----- make sure we keep UI consistent
  updateGUI();
  // -----
  kdDebug() << "TopLevelWidget::entrySelected: done (" << index
	    << ")." << endl;
}

void TopLevelWidget::entriesChanged()
{ // this signal is emitted from AddressBook::updateEntriesMap
  kdDebug() << "TopLevelWidget::entriesChanged: called." << endl;
  // -----
  int current;
  QComboBox *combo=toolBar(EntryNavigation)->getCombo(Entries);
  QStringList headlines;
  // -----
  if(combo==0) return;
  if(api->addressbook()->getListOfNames(&headlines, true, false)
     !=AddressBook::NoError)
    {
      return;
    }
  current=combo->currentItem();
  if(current<0) current=0;
  combo->clear();
  combo->insertStringList(headlines);
  current=QMIN(combo->count()-1, current);
  combo->setCurrentItem(current);
  entrySelected(current);
  updateGUI();
  // -----
  kdDebug() << "TopLevelWidget::entriesChanged: done." << endl;
}

void TopLevelWidget::importKab1Addressbook()
{ // WORK_TO_DO: Proof-read messages
  // ###########################################################################
  Kab1Importer importer(api, this);
  connect(&importer, SIGNAL(setStatus(const QString&)),
	  SLOT(setStatus(const QString&)));
  if(importer.exec())
    {
      entriesChanged();
    }
  // ###########################################################################
}

void TopLevelWidget::setStatus(const QString& text)
{
  // ###########################################################################
  messages->append(text);
  if(!timer->isActive())
    {
      timer->start(0);
    } else {
      timer->start(500); // WORK_TO_DO: implement preference for duration
    }
  // ###########################################################################
}

void TopLevelWidget::statusbarTimeOut()
{
  // ###########################################################################
  if(!messages->isEmpty())
    {
      statusBar()->changeItem(messages->first(), Text);
      messages->remove(messages->begin());
      if(messages->isEmpty())
	{
	  timer->start(5000, true);
	} else {
	  timer->start(1000, true);
	}
    } else {
      statusBar()->changeItem("", Text);
    }
  // ###########################################################################
}

void TopLevelWidget::loadDefaultDatabase()
{
  // ###########################################################################
  QString path;
  // -----
  path=api->addressbook()->getStandardFilename();
  if(path.isEmpty())
    {
      KMessageBox::sorry
	(this, i18n("Cannot locate default database."),
	 i18n("Critical failure"));
    } else {
      if(api->addressbook()->load(path)!=AddressBook::NoError)
	{
	  KMessageBox::sorry
	    (this, i18n("The standard file could not be loaded."),
	     i18n("File error"));
	}
    }
  modified=false;
  updateGUI();
  // ###########################################################################
}

void TopLevelWidget::slotSelectViewBC(bool state)
{
  // menuview->setItemChecked(idViewBC, true);
  // menuview->setItemChecked(idViewEdit, false);
  if(state)
    {
      createView(BusinessCard);
      emit(setStatus(i18n("Business card view.")));
    }
}

void TopLevelWidget::slotSelectViewEdit(bool state)
{
  // menuview->setItemChecked(idViewBC, false);
  // menuview->setItemChecked(idViewEdit, true);
  if(state)
    {
      createView(Editing);
      emit(setStatus(i18n("Editing view.")));
    }
}

void TopLevelWidget::enableAllMessages()
{
  if(KMessageBox::questionYesNo
     (this,
      i18n("This will re-enable all messages you disabled before.\n"
	   "Continue?"),
      i18n("Enable messages?"))==KMessageBox::Yes)
    {
      KMessageBox::enableAllMessages();
    }
}

void TopLevelWidget::add()
{
  KabKey key;
  AddressBook::Entry entry; // WORK_TO_DO: possibly use templates or
  // default entries?
  int index;
  // -----
  if(editEntry(entry))
    {
      switch(api->add(entry, key, true))
	{
	case AddressBook::NoError:
	  // ----- first save the database:
	  emit(setStatus(i18n("New entry added.")));
	  emit(setStatus(i18n("Saving new entry.")));
	  save();
	  emit(setStatus(i18n("Done.")));
	  emit(databaseChanged());
	  entriesChanged();
	  // ----- make the new entry the current (and visible):
	  if(api->addressbook()->getIndex(key, index)==AddressBook::NoError)
	    {
	      bool dummy;
	      QComboBox *combo;
	      combo=toolBar(EntryNavigation)->getCombo(Entries);
	      CHECK_PTR(combo);
	      combo->setCurrentItem(index); // this does not emit a signal, thus...
	      dummy=modified; modified=false;
	      entrySelected(index);
	      modified=dummy;
	    }
	  break;
	case AddressBook::PermDenied:
	  KMessageBox::information
	    (this,
	     i18n("Cannot add the new entry.\n"
		  "Permission denied.\n"
		  "You may not have writing permissions for this file."),
	     i18n("Error"));
	  return;
	default:
	  KMessageBox::information
	    (this,
	     i18n("Cannot add the new entry.\n"
		  "Unknown cause."),
	     i18n("Error"));
	  return;
	}
    } else {
      emit(setStatus(i18n("No entry added.")));
    }
}

void TopLevelWidget::remove()
{
  removeCurrentEntry();
}

bool TopLevelWidget::removeCurrentEntry(bool quiet)
{
  bool queryOnDelete=true; // WORK_TO_DO: respect configuration settings
  bool doIt=true;
  // -----
  if(queryOnDelete && !quiet)
    {
      doIt=KMessageBox::questionYesNo
	(this,
	 i18n("Really remove this entry?"),
	 i18n("Question"))==KMessageBox::Yes;
    }
  if(doIt)
    {
      if(api->remove(*current)==AddressBook::NoError)
	{
	  emit(setStatus(i18n("Entry deleted.")));
	  save();
	  emit(setStatus(i18n("Database saved.")));
	  emit(databaseChanged());
	  entriesChanged();
	  return true;
	} else {
	  KMessageBox::information
	    (this,
	     i18n("Could not delete the entry\n"
		  "(probably permission denied)."),
	     i18n("Error"));
	  emit(setStatus(i18n("Permission denied.")));
	  return false;
	}
    }
  return false;
}

void TopLevelWidget::createView(View view, bool recreate)
{
  // bool modificationstate=modified;
  mainwidget->createView(view, recreate);
  // modified=modificationstate;
  updateGUI();
}

void TopLevelWidget::entryChangedSlot()
{
  modified=true;
}

void TopLevelWidget::quit()
{
  register bool GUARD; GUARD=false;
  kdDebug() << "TopLevelWidget::quit: quitting." << endl;
  queryClose();
  kapp->quit();
}

void TopLevelWidget::showAboutApplication(void)
{
  QPixmap logo;
  QString path;
  KAboutDialog dialog(this);
  // -----
  path=locate("appdata", "pics/addressbook_logo.png");
  if(path.isEmpty())
    {
      kdDebug() << "TopLevelWidget::aboutKAB: cannot locate"
		<< " ressources." << endl;
    } else {
      if(logo.load(path))
	{
	  dialog.setLogo(logo);
	}
    }
  dialog.setCaption("About KDE address book");
  dialog.setVersion("KDE address book 2.0alpha");
  dialog.setAuthor("Mirko Boehm", "mirko@kde.org", "", "Initial developer.");
  dialog.adjust();
  dialog.exec();
}

void TopLevelWidget::mail()
{
  mail(QString::null);
}

void TopLevelWidget::mail(const QString&)
{ // the given URL is ignored!
  AddressBook::Entry entry;
  int x;
  KDialogBase dialog
    (this, 0, true, i18n("Send an email"), KDialogBase::Ok|KDialogBase::Cancel,
     KDialogBase::Ok, true);
  QListBox lb(&dialog);
  dialog.setMainWidget(&lb);
  QString email, name;
  // ----- get the entry:
  mainwidget->getView()->getEntry(entry);
  if(entry.emails.count()==0)
    {
      emit(setStatus(i18n("No email addresses.")));
      KNotifyClient::beep();
      return;
    }
  if(entry.emails.count()>1)
    {
      lb.insertStringList(entry.emails);
      lb.setCurrentItem(0);
      x=QMAX((int)(lb.maxItemWidth()*1.10), lb.sizeHint().width());
      lb.setMinimumSize(x, QMAX(x/2, lb.sizeHint().height()));
      if(dialog.exec())
	{ 
	  email=lb.currentText();
	} else {
	  emit(setStatus(i18n("Rejected.")));
	  KNotifyClient::beep();
	  return;
	}
    } else {
      email=entry.emails.first();
    }
  // ----- add name etc, mail address in brackets:
  if(api->addressbook()->literalName(entry, name)==AddressBook::NoError)
    {
      email=name+" <"+email+">";
    }
  // ----- open the composer:
  kapp->invokeMailer(email, QString::null);
}

void TopLevelWidget::browse()
{
  browse(QString::null);
}

void TopLevelWidget::browse(const QString&)
{ // the given URL is ignored!
  AddressBook::Entry entry;
  int x;
  KDialogBase dialog
    (this, 0, true, i18n("Browse to ..."), KDialogBase::Ok|KDialogBase::Cancel,
     KDialogBase::Ok, true);
  QListBox lb(&dialog);
  dialog.setMainWidget(&lb);
  // ----- get the entry:
  mainwidget->getView()->getEntry(entry);
  if(entry.URLs.count()==0)
    {
      emit(setStatus(i18n("No URLs.")));
      KNotifyClient::beep();
      return;
    }
  if(entry.URLs.count()>1)
    {
      lb.insertStringList(entry.URLs);
      lb.setCurrentItem(0);
      x=QMAX((int)(lb.maxItemWidth()*1.10), lb.sizeHint().width());
      lb.setMinimumSize(x, QMAX(x/2, lb.sizeHint().height()));
      if(dialog.exec())
	{ // ----- open the composer:
	  kapp->invokeBrowser(lb.currentText());
	} else {
	  emit(setStatus(i18n("Rejected.")));
	  KNotifyClient::beep();
	  return;
	}
    } else {
      kapp->invokeBrowser(entry.URLs.first());
    }
}

bool TopLevelWidget::queryClose()
{ // WORK_TO_DO: to query or not to query? There is no unsaved data,
  // but we have to do some cleanup. // quit();
  kdDebug() << "TopLevelWidget::queryClose: called." << endl;
  closingdown=true;
  if(modified)
    {
      kdDebug() << "TopLevelWidget::queryClose: saving modified database."
		<< endl;
      save();
    }
  writeConfiguration();
  return true;
}

void TopLevelWidget::loadConfiguration()
{
  Section* config;
  KeyValueMap *prefs;
  QConfigDB *db;
  // -----
  db=api->addressbook()->getConfig();
  connect(db, SIGNAL(fileChanged()), SLOT(loadConfiguration()));
  db->watch(true);
  if(db->get("config", config))
    {
      prefs=config->getKeys();
    } else { // set defaults
      showAssembledNameInWindowTitle=true;
      rememberLastView=true;
      rememberLastEntry=true;
      return;
    }
  if(prefs->get("RememberLastView", rememberLastView))
    {
      if(rememberLastView)
	{
	  emit(setStatus(i18n("Restoring last view.")));
	} else {
	  emit(setStatus(i18n("Not restoring last view.")));
	}
    } else {
      kdDebug() << "TopLevelWidget::loadConfiguration: no preference setting"
		<< " for RememberLastView" << endl;
      rememberLastView=true;
    }
  if(prefs->get("RememberLastEntry", rememberLastEntry))
    {
      if(rememberLastEntry)
	{
	  emit(setStatus(i18n("Remembering last entry.")));
	} else {
	  emit(setStatus(i18n("Not remembering last entry.")));
	}
    } else {
      kdDebug() << "TopLevelWidget::loadConfiguration: no preference setting"
		<< " for RememberLastEntry" << endl;
      rememberLastEntry=true;
    }
  if(prefs->get("ShowNameInWindowTitle", showAssembledNameInWindowTitle))
    {
      if(showAssembledNameInWindowTitle)
	{
	  emit(setStatus(i18n("Displaying assembled name in window title.")));
	} else {
	  emit(setStatus(i18n("Not displaying assembled name in window title.")));
	}
    } else {
      kdDebug() << "TopLevelWidget::loadConfiguration: no preference setting"
		<< " for ShowNameInWindowTitle" << endl;
      showAssembledNameInWindowTitle=true;
    }
  if(prefs->get("CreateBackupOnStartup", createBackupOnStartup))
    {
      if(createBackupOnStartup)
	{
	  emit(setStatus(i18n("Creating backup file on startup.")));
	} else {
	  emit(setStatus(i18n("Not creating a backup file on startup.")));
	}
    } else {
      kdDebug() << "TopLevelWidget::loadConfiguration: no preference setting"
		<< " for CreateBackupOnStartup" << endl;
      createBackupOnStartup=true;
    }
}

void TopLevelWidget::writeConfiguration()
{
  KConfig *config = kapp->config();
  QString str;

  saveMainWindowSettings(config, "Main Window");
  // config->sync();
}

void TopLevelWidget::updateGUI()
{
  bool hasEmailAddresses, hasURLs;
  // -----
  if(closingdown) return;
  AddressBook::Entry entry;
  int number;
  // -----
  if(mainwidget->getView()==0) return; // prevent crashes in final versions
  mainwidget->getView()->getEntry(entry);
  number=api->addressbook()->noOfEntries();
  hasEmailAddresses=entry.emails.count()>0;
  hasURLs=entry.URLs.count()>0;
  // ----- enable/disable buttons:
  if(number<2)
    {
      actionFirstEntry->setEnabled(false);
      actionPreviousEntry->setEnabled(false);
      actionNextEntry->setEnabled(false);
      actionLastEntry->setEnabled(false);
    } else {
      if(currentIndex==0)
	{
	  actionFirstEntry->setEnabled(false);
	  actionPreviousEntry->setEnabled(false);
	} else {
	  actionFirstEntry->setEnabled(true);
	  actionPreviousEntry->setEnabled(true);
	}
      if(currentIndex==number-1)
	{
	  actionNextEntry->setEnabled(false);
	  actionLastEntry->setEnabled(false);
	} else {
	  actionNextEntry->setEnabled(true);
	  actionLastEntry->setEnabled(true);
	}
    }
  if(showAssembledNameInWindowTitle) // already read from prefs
    {
      bool initials=false; // WORK_TO_DO: read from prefs
      bool reverse=false; // WORK_TO_DO: read from prefs
      QString text;
      api->addressbook()->literalName(entry, text, reverse, initials);
      setCaption(text);
    } else {
      setCaption("");
    }
  switch(api->addressbook()->getState())
    {
    case AddressBook::NoError: // a file has been loaded without errors
      // ----- menu items:
      actionNewEntry->setEnabled(true);
      actionEditEntry->setEnabled(mainwidget->viewType()!=Editing && number>0);
      actionDeleteEntry->setEnabled(number>0);
      actionSendEmail->setEnabled(hasEmailAddresses && number>0);
      actionBrowse->setEnabled(hasURLs && number>0);
      // ----- toolbar buttons:
      mainwidget->getView()->setEnabled(number>0);
      break;
    case AddressBook::NoFile: // no file has been loaded
    case AddressBook::PermDenied: // error loading the file
      actionNewEntry->setEnabled(false);
      actionEditEntry->setEnabled(false);
      actionDeleteEntry->setEnabled(false);
      actionSendEmail->setEnabled(false);
      actionBrowse->setEnabled(false);
      mainwidget->getView()->setEnabled(false);
      break;
    default:
      kdDebug() << "TopLevelWidget::updateGUI: "
		<< "Unknown API state in switch." << endl;
    }
  // ----- other actions:
  actionShowSearchResults->setEnabled(mainwidget->searchresults()->hasResults());
  // ----- keep sync:
  actionShowMainToolbar->setChecked(toolBar(MainToolBar)->isVisible());
  actionShowEntryNavToolbar->setChecked(toolBar(EntryNavigation)->isVisible());
}

void TopLevelWidget::keyPressEvent(QKeyEvent *e)
{
  switch(e->key())
    {
    case Key_Left:
      kdDebug() << "TopLevelWidget::keyPressEvent: first entry (Key_Left)." << endl;
      slotFirstEntry();
      e->accept();
      break;
    case Key_Up:
      kdDebug() << "TopLevelWidget::keyPressEvent: previous entry (Key_Up)." 
		<< endl;
      slotPreviousEntry();
      e->accept();
      break;
    case Key_Down:
      kdDebug() << "TopLevelWidget::keyPressEvent: next entry (Key_Down)." << endl;
      slotNextEntry();
      e->accept();
      break;
    case Key_Right:
      kdDebug() << "TopLevelWidget::keyPressEvent: last entry (Key_Right)." << endl;
      slotLastEntry();
      e->accept();
      break;
    default:
       e->ignore();
    }
}

void TopLevelWidget::slotSearchEntries()
{
  QStringList headlines;
  AddressBook::Entry entry;
  KabKey key;
  int count;
  QString temp;
  char* field;
  BunchOfKeys *keys;
  QStringList matches;
  QList<int> indizes;
  bool addressField=false; // true if we are looking for a field in the addresses
  bool match;
  QVariant value;
  KDialogBase dialog(this, 0, true,
		     i18n("Search entries"),
		     KDialogBase::Ok|KDialogBase::Cancel,
		     KDialogBase::Ok, true);
  SearchWidget search(&dialog);
  // ----- fill in all field descriptions:
  dialog.setMainWidget(&search);
  for(count=0; count<AddressBook::Entry::NoOfFields; ++count)
    {
      if(AddressBook::Entry::nameOfField(AddressBook::Entry::Fields[count], temp))
	{
	  search.cbFields->insertItem(temp);
	} else {
	  search.cbFields->insertItem(i18n("(ERROR)"));
	}
    }
  for(count=0; count<AddressBook::Entry::Address::NoOfFields; ++count)
    {
      if(AddressBook::Entry::Address::nameOfField
	 (AddressBook::Entry::Address::Fields[count], temp))
	{
	  search.cbFields->insertItem(temp);
	} else {
	  search.cbFields->insertItem(i18n("(ERROR)"));
	}
    }
  search.cbFields->setCurrentItem(6); // the last name
  // ----- execute the dialog:
  if(dialog.exec())
    {
      if(search.cbFields->currentItem()<AddressBook::Entry::NoOfFields)
	{ // we are searching for an Entry field
	  addressField=false;
	  field=(char*)AddressBook::Entry::Fields[search.cbFields->currentItem()];
	} else { // it is a Entry::Address field
	  addressField=true;
	  field=(char*)AddressBook::Entry::Address::Fields
	    [search.cbFields->currentItem()-AddressBook::Entry::NoOfFields];
	}
#if ! defined NDEBUG
      addressField 
	? AddressBook::Entry::Address::nameOfField(field, temp)
	: AddressBook::Entry::nameOfField(field, temp);
      kdDebug() << "TopLevelWidget::slotSearchEntries: searching for " 
		<< temp << " (key " << field << ") ["
		<< (addressField ? "address field" : "entry field")
		<< "]." << endl;
#endif
      // ----- now do the search, thereby creating a set of KabKeys that
      //       point to entries matching the pattern
      //       first get a list of headlines, we can get the entries by their 
      //       position in the list (better user feedback possible...):
      keys=new BunchOfKeys;
      if(api->addressbook()->getListOfNames(&headlines, true, false)
	 !=AddressBook::NoError)
	{
	  kdDebug() << "TopLevelWidget::slotSearchEntries: cannot get " 
		    << "headlines." << endl;
	  KMessageBox::information
	    (this,
	     i18n("Could not get an overview over the existing entries."),
	     i18n("Internal Error"));
	  return;
	}
      // ----- now iterate over the entries in the order given in the list 
      //       (that is the same as in the combos/lists etc):
      for(count=0; (unsigned)count<headlines.count(); ++count)
	{ // if this takes long, maybe a progress bar would be nice
	  kdDebug() << "TopLevelWidget::slotSearchEntries: verifying " 
		    << *headlines.at(count) << "." << endl;
	  // ----- query the key for the entry at that index:
	  if(api->addressbook()->getKey(count, key)!=AddressBook::NoError)
	    {
	      kdDebug() << "TopLevelWidget::slotSearchEntries: cannot get " 
			<< "key for entry at existing index." << endl;
	      KMessageBox::information
		(this,
		 i18n("Could not query an existing entry."),
		 i18n("Internal Error"));
	      return;
	    }
	  // ----- now get the entry itselfes:
	  if(api->addressbook()->getEntry(key, entry)!=AddressBook::NoError)
	    {
	      kdDebug() << "TopLevelWidget::slotSearchEntries: cannot get " 
			<< "entry for existing key." << endl;
	      KMessageBox::information
		(this,
		 i18n("Could not query an existing entry."),
		 i18n("Internal Error"));
	      return;
	    }
	  QRegExp regexp(search.lePattern->text(), 
			 search.cbCaseSensitive->isChecked(),
			 search.rbWildcards->isChecked());
	  // ----- now compare it:
	  if(addressField)
	    { 
	      match=false;
	      for(int add=0; add<entry.noOfAddresses(); ++add)
		{ // iterate over the addresses
		  AddressBook::Entry::Address address;
		  // -----
		  if(entry.getAddress(add, address)==AddressBook::NoError)
		    {
		      if(address.get(field, value)!=AddressBook::NoError)
			{
			  kdDebug() << "TopLevelWidget::slotSearchEntries: "
				    << "field name is undefined!" << endl;
			  match=false;
			} else {
			  if(regexp.find(value.asString(), 0)!=-1)
			    { 
			      match=true;
			      break;
			    }
			}
		    }
		}
	    } else {
	      // ----- get the value:
	      if(entry.get(field, value)!=AddressBook::NoError)
		{
		  kdDebug() << "TopLevelWidget::slotSearchEntries: "
			    << "field name is undefined!" << endl;
		  match=false;
		} else {
		  // ----- compare it to the given pattern:
		  if(regexp.find(value.asString(), 0)!=-1)
		    { 
		      match=true;
		    } else {
		      match=false;
		    }
		}
	    }
	  if(match)
	    {
	      kdDebug() << "TopLevelWidget::slotSearchEntries: match." << endl;
	      keys->push_back(KabKey(key));
	      matches.append(*headlines.at(count));
	      indizes.append(new int(count));
	    } else {
	      kdDebug() << "TopLevelWidget::slotSearchEntries: no match." << endl;
	    }
	}
      kdDebug() << "TopLevelWidget::slotSearchEntries: found " << keys->size()
		<< " matches." << endl;
      // ----- now set the search reults and show the search results window:
      if(matches.count()>0)
	{
	  QString temp;
	  // -----
	  mainwidget->searchresults()->setContents(matches, indizes);
	  temp.setNum(matches.count());
	  mainwidget->searchresults()->labelHeader->setText
	    (i18n("Search results (") + temp + i18n(" hits):"));
	  slotShowSearchResults(true);
	} else {
	  KMessageBox::sorry
	    (this, 
	     i18n("Sorry!\nNo entry matched your criterion."),
	     i18n("No hits"));
	}
      // ----- done
    } else {
      emit(setStatus(i18n("Rejected.")));
      KNotifyClient::beep();
    }
  // REMEMBER: remove BunchOfKeys if not needed.
}

void TopLevelWidget::slotShowSearchResults(bool b)
{
  kdDebug() << "TopLevelWidget::slotShowSearchResults: "
	    << (b ? "displaying " : "hiding ")
	    << "search results." << endl;
  actionShowSearchResults->setChecked(b);
  mainwidget->slotShowSearchResults(b);
  updateGUI();
}

void TopLevelWidget::slotActivateEntry(int index)
{
  activateEntry(index); // this is not a slot
}

void TopLevelWidget::slotShowMainToolbar(bool state)
{
  const char* key=MainToolBar;
  // -----
  if(state)
    {
      toolBar(key)->show();
    } else {
      toolBar(key)->hide();
    }
  kdDebug() << "TopLevelWidget::slotShowMainToolbar: " << key 
	    << " toolbar is "
	    << (toolBar(key)->isVisible() ? "visible" : "invisible")
	    << "." << endl;
}

void TopLevelWidget::slotShowEntryNavToolbar(bool state)
{
   const char* key=EntryNavigation;
  // -----
  if(state)
    {
      toolBar(key)->show();
    } else {
      toolBar(key)->hide();
    }
  kdDebug() << "TopLevelWidget::slotShowEntryNavToolbar: " << key 
	    << " toolbar is "
	    << (toolBar(key)->isVisible() ? "visible" : "invisible")
	    << "." << endl;
}

void TopLevelWidget::showEvent(QShowEvent*)
{
  updateGUI();
}

void TopLevelWidget::slotPrintSearchResults()
{
  kdDebug() << "TopLevelWidget::slotPrintSearchResults: "
	    << "called." << endl;
  BunchOfKeys keys;
  // -----
  mainwidget->searchresults()->bunchOfKeys(keys);
  printEntries(keys);
}

#include "kab_topwidget.moc"

