/* This file implements kabīs toplevel widget..
 *
 * the KDE addressbook
 * copyright:  (C) Mirko Sucker, 1998, 1999, 2000
 * mail to:    Mirko Sucker <mirko@kde.org>
 * requires:   recent C++-compiler, at least Qt 2.0
 
 This library 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 library 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 library; see the file COPYING.LIB.  If not, write to
 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 Boston, MA 02111-1307, USA.
 
 * $Revision: 1.28 $
 */

#include "kab_topwidget.h"
#include "kab_kab1importer.h"
#include <kabapi.h>
#include "look_basic.h"
#include "look_businesscard.h"
#include "look_edit.h"
#include "widget_datanavigator.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 <kaboutdialog.h>
#include <qtimer.h>
#include <qframe.h>
#include <qstringlist.h>
#include <qlayout.h>
#include <stdio.h>
#include <kdebug.h>

TopLevelWidget::TopLevelWidget()
  : KTMainWindow(),
    viewFrame(0),
    view(0),
    currentView(NoView),
    current(0),
    hasEmailAddresses(false),
    hasURLs(false),
    timer(new QTimer(this)),
    messages(new QStringList),
    modified(false),
    closingdown(false)
{
  bool GUARD; GUARD=true;
  kdDebug() << "TopLevelWidget ctor: called.";
  // -----
  // WORK_TO_DO: remember last selected view
  createView(BusinessCard);
  api=new KabAPI(this);
  if(api==0)
    {
      KMessageBox::sorry
	(this, i18n("Out of memory."),
	 i18n("General failure"));
      ::exit(-1);
    }
  // -----
  makeMenu();
  makeToolbar();
  makeStatusbar();
  // -----
  // connect(nav, SIGNAL(itemSelected(int)), SLOT(entrySelected(int)));
  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."),
	 i18n("Database error"));
      ::exit(-1);
    }
  connect(this, SIGNAL(databaseChanged()),
	  api->addressbook(), SLOT(externalChange()));
  // -----
  entriesChanged();
  // -----
  setStatus(i18n("Welcome to kab 2.0"));
  kdDebug() << "TopLevelWidget ctor: done.";
}

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()));
  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()));
  idFileSave=file->insertItem(i18n("&Save"), this,
			      SLOT(save()), KStdAccel::save());
  file->insertSeparator();
  file->insertItem(i18n("&Import..."), import);
  file->insertSeparator();
  file->insertItem(i18n("&Quit"), this, SLOT(quit()), KStdAccel::quit());
  connect(kapp, SIGNAL(shutDown()), SLOT(quit()));
  // ----- the edit menu:
  edit=new QPopupMenu;
  edit->insertItem(i18n("&Add entry"), this, SLOT(add()), CTRL+Key_A);
  idEditEdit=edit->insertItem
    (i18n("&Edit entry"), this, SLOT(editEntry()), CTRL+Key_E);
  idEditRemove=edit->insertItem
    (i18n("&Remove entry"), this, SLOT(remove()), CTRL+Key_R);
  edit->insertSeparator();
  idEditMail=edit->insertItem
    (i18n("Send an e&mail"), this, SLOT(mail()), CTRL+Key_M);
  idEditBrowse=edit->insertItem
    (i18n("&Browse"), this, SLOT(browse()), CTRL+Key_B);
  edit->insertSeparator();
  edit->insertItem(i18n("Enable all &messages"), this,
		   SLOT(enableAllMessages()));
  edit->insertItem(i18n("&Configure this file"), this,
		   SLOT(configureFile()));
  // ----- the view menu:
  menuview=new QPopupMenu;
  idViewBC=menuview->insertItem(i18n("&Business card"), this,
				SLOT(selectViewBC()));
  idViewEdit=menuview->insertItem(i18n("&Editing"), this,
				  SLOT(selectViewEdit()));
  menuview->setCheckable(true);
  selectViewBC();
  // -----
  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;
  // -----
  toolBar()->insertButton(BarIcon("filenew"), NewEntry, true,
			  i18n("New entry"));
  toolBar()->insertButton(BarIcon("pencil"), EditEntry, true,
			  i18n("Edit entry"));
  toolBar()->insertButton(BarIcon("eraser"), DeleteEntry, true,
			  i18n("Delete entry"));
  toolBar()->insertLineSeparator();
  toolBar()->insertButton(BarIcon("start"), FirstEntry, true,
			  i18n("First entry"));
  toolBar()->insertButton(BarIcon("back"), PrevEntry, true,
			  i18n("Previous entry"));
  toolBar()->insertCombo(dummy, Entries, false,
			 SIGNAL(activated(int)),
			 this, SLOT(entrySelected(int)));
  toolBar()->setItemAutoSized(Entries);
  toolBar()->insertButton(BarIcon("forward"), NextEntry, true,
			  i18n("Next entry"));
  toolBar()->alignItemRight(NextEntry);
  toolBar()->insertButton(BarIcon("finish"), LastEntry, true,
			  i18n("Last entry"));
  toolBar()->alignItemRight(LastEntry);
  connect(toolBar(), SIGNAL(clicked(int)), SLOT(action(int)));
}

void TopLevelWidget::action(int i)
{
  QComboBox *combo=toolBar()->getCombo(Entries);
  CHECK_PTR(combo);
  int index;
  // -----
  switch(i)
    {
    case NewEntry: // add
      add();
      break;
    case EditEntry: // edit
      editEntry();
      break;
    case DeleteEntry: // delete
      remove();
      break;
    case FirstEntry:
      index=0;
      combo->setCurrentItem(index);
      entrySelected(index);
      break;
    case PrevEntry:
      index=QMAX(0, combo->currentItem()-1);
      combo->setCurrentItem(index);
      entrySelected(index);
      break;
    case NextEntry:
      index=QMIN(combo->count()-1, combo->currentItem()+1);
      combo->setCurrentItem(index);
      entrySelected(index);
      break;
    case LastEntry:
      index=combo->count()==0 ? 0 : combo->count()-1;
      combo->setCurrentItem(index);
      entrySelected(index);
      break;
    default:
      debug("TopLevelWidget::action: unknown action %i", i);
    };
}


void TopLevelWidget::makeStatusbar()
{
  //statusBar()->setInsertOrder(KStatusBar::RightToLeft);
  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."),
	 i18n("Database error"));
      return false;
    } else {
      connect(api->addressbook(), SIGNAL(changed()),
	      SLOT(entriesChanged()));
      return true;
    }
}

void TopLevelWidget::entrySelected(int index)
{
  bool GUARD; GUARD=true;
  kdDebug() << "TopLevelWidget::entrySelected: called ( " << index << ")";
  // -----
  AddressBook::Entry entry;
  KabKey key;
  int number;
  QString text;
  // ----- store user changes:
  if(closingdown) return;
  if(modified) // the current entry has been modified
    { // 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)
	    {
	      save();
	    }
	}
    }
  // -----
  number=api->addressbook()->noOfEntries();
  kdDebug() << "TopLevelWidget::entrySelected: " << number <<" entries.";
  if(number!=0)
    {
      view->setEnabled(true);
      if(api->addressbook()->getKey(index, key)!=AddressBook::NoError)
	{
	  kdDebug() << "TopLevelWidget::entrySelected: no such entry " << index;
	}
      if(api->addressbook()->getEntry(key, entry)!=AddressBook::NoError)
	{
	  kdDebug() << "TopLevelWidget::entrySelected: cannot access existing entry.";
	}
    } else {
      kdDebug() << "TopLevelWidget::entrySelected: no entries.";
      view->setEnabled(false);
    }
  // -----
  if(number==0)
    {
      text=i18n("No entries.");
    } else {
      text=text.sprintf("%i/%i", index+1, number);
    }
  statusBar()->changeItem(text, Number);
  // -----
  view->setEntry(entry);
  hasEmailAddresses=entry.emails.count()>0;
  hasURLs=entry.URLs.count()>0;
  modified=false; // finally reset modification mark
  if(current!=0)
    { // make sure the old key is deleted:
      delete current; current=0;
    }
  current=new KabKey(key);
  // ----- enable/disable buttons:
  if(number<2)
    {
      toolBar()->setItemEnabled(FirstEntry, false);
      toolBar()->setItemEnabled(PrevEntry, false);
      toolBar()->setItemEnabled(LastEntry, false);
      toolBar()->setItemEnabled(NextEntry, false);
    } else {
      if(index==0)
	{
	  toolBar()->setItemEnabled(FirstEntry, false);
	  toolBar()->setItemEnabled(PrevEntry, false);
	} else {
	  toolBar()->setItemEnabled(FirstEntry, true);
	  toolBar()->setItemEnabled(PrevEntry, true);
	}
      if(index==number-1)
	{
	  toolBar()->setItemEnabled(LastEntry, false);
	  toolBar()->setItemEnabled(NextEntry, false);
	} else {
	  toolBar()->setItemEnabled(LastEntry, true);
	  toolBar()->setItemEnabled(NextEntry, true);
	}
    }
  enableButtons();
  // -----
  kdDebug() << "TopLevelWidget::entrySelected: done " << index << ".";
}

void TopLevelWidget::entriesChanged()
{ // this signal is emitted from AddressBook::updateEntriesMap
  bool GUARD; GUARD=true;
  kdDebug() << "TopLevelWidget::entriesChanged: called.";
  // -----
  int current;
  QComboBox *combo=toolBar()->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=QMAX(combo->count()-1, current);
  combo->setCurrentItem(current);
  entrySelected(current); // WORK_TO_DO: remember the visible entry like in kab 1
  enableButtons();
  // -----
  kdDebug() << "TopLevelWidget::entriesChanged: done.";  
}

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);
    }
  // ###########################################################################
}

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())
    {
      if(api->addressbook()->load(path)!=AddressBook::NoError)
	{
	  KMessageBox::sorry
	    (this, i18n("The standard file could not be loaded."),
	     i18n("File error"));
	}
    }
  modified=false;
  enableButtons();
  // ###########################################################################
}

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

void TopLevelWidget::selectViewEdit()
{
  menuview->setItemChecked(idViewBC, false);
  menuview->setItemChecked(idViewEdit, true);
  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()
{ // WORK_TO_DO: check for redundant calls 
  KabKey key;
  AddressBook::Entry entry; // WORK_TO_DO: possibly use templates or default entries?
  int index;
  // -----
  switch(api->add(entry, key, true))
    {
    case AddressBook::NoError:
      // ----- first save the database:
      emit(setStatus(i18n("New entry added.")));
      save();
      emit(databaseChanged());
      entriesChanged();
      // ----- make the new entry the current (and visible):
      if(api->addressbook()->getIndex(key, index)==AddressBook::NoError)
	{
	  entrySelected(index);
	} 
      // ----- and edit it if we are not in editing mode:
      if(currentView!=Editing)
	{
	  if(*current==key)
	    {
	      if(editCurrentEntry())
		{
		  emit(i18n("Saving new entry."));
		  save();
		  emit(i18n("Done."));
		} else {
		  emit(i18n("Rejected."));
		  removeCurrentEntry(true); // silently
		}
	    } else {
	      KMessageBox::information
		(this,
		 i18n("Cannot display the new entry.\n"
		      "Unknown cause."),
		 i18n("Error"));
	      return;
	    }
	}
      break;
    case AddressBook::PermDenied:
      KMessageBox::information
	(this,
	 i18n("Cannot add the new entry.\n"
	      "Permission denied.\n"
	      "You do 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;
    }     
  emit(databaseChanged());
  entriesChanged();
  // ----- recalculate index of this entry (may have changed):
  if(api->addressbook()->getIndex(key, index)==AddressBook::NoError)
    {
      entrySelected(index);
    } 
}

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(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 v, bool recreate)
{
  bool modificationstate=modified;
  // ----- avoid deleting and creating:
  if(v==currentView && !recreate) return;
  // ----- create view frame if needed:
  if(viewFrame==0)
    { // this should be called only once:
      viewFrame=new QFrame(this);
      QBoxLayout *viewLayout=new QBoxLayout
	(viewFrame, QBoxLayout::TopToBottom);
      viewLayout->setAutoAdd(true);
      setView(viewFrame, true);
    }
  // -----
  AddressBook::Entry entry;
  // -----
  if(view!=0)
    {
      view->getEntry(entry);
      delete view;
      view=0;
    }
  // -----
  switch(v)
    {
    case BusinessCard:
      view=new KABBusinessCard(api, viewFrame);
      break;
    case Editing:
      view=new KABEditLook(api, viewFrame);
      connect(view, SIGNAL(entryChanged()), SLOT(entryChangedSlot()));
      break;
    default:
      kdDebug() << "TopLevelWidget::createView: unknown kind of view.";
      view=new KABBusinessCard(api, viewFrame);
    }
  connect(view, SIGNAL(sendEmail(const QString&)), SLOT(mail(const QString&)));
  connect(view, SIGNAL(browse(const QString&)), SLOT(browse(const QString&)));
  view->setEntry(entry);
  viewFrame->setMinimumSize(320, 200);
  //  setView(view, true);
  view->show();
  currentView=v;
  modified=modificationstate;
  // -----
}

void TopLevelWidget::entryChangedSlot()
{
  register bool GUARD; GUARD=false;
  kdDebug() << "TopLevelWidget::entryChangedSlot: current entry has been changed.";
  modified=true;
}

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

void TopLevelWidget::aboutKDE()
{
}

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.";
    } else {
      if(logo.load(path))
	{
	  dialog.setLogo(logo);
	}
    }
  dialog.setCaption("About KDE address book");
  dialog.setVersion("KDE address book 2.0alpha");
  dialog.setAuthor("Mirko Sucker", "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);
  // ----- get the entry:
  if(api->addressbook()->getEntry(*current, entry)==AddressBook::NoError)
    {
      if(entry.emails.count()==0)
	{
	  emit(setStatus(i18n("No email addresses.")));
	  kapp->beep();
	  return;
	}
      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())
	{ // ----- open the composer:
	  kapp->invokeMailer(lb.currentText(), QString::null);
	} else {
	  emit(setStatus(i18n("Rejected.")));
	  kapp->beep();
	  return;
	}
    } else {
      kdDebug() << "TopLevelWidget::mail: cannot find current entry.";
    }
}

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:
  if(api->addressbook()->getEntry(*current, entry)==AddressBook::NoError)
    {
      if(entry.emails.count()==0)
	{
	  emit(setStatus(i18n("No URLs.")));
	  kapp->beep();
	  return;
	}
      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.")));
	  kapp->beep();
	  return;
	}
    } else {
      debug("TopLevelWidget::browse: cannot find current entry.");
    }
}

void TopLevelWidget::enableButtons()
{
  int entries;
  // ----- menu items:
  entries=api->addressbook()->noOfEntries();
  edit->setItemEnabled
    (idEditEdit, currentView!=Editing && entries>0);
  edit->setItemEnabled(idEditRemove, entries>0);
  edit->setItemEnabled(idEditMail, hasEmailAddresses && entries>0);
  edit->setItemEnabled(idEditBrowse, hasURLs && entries>0);
  // ----- toolbar buttons:
  toolBar()->setItemEnabled(EditEntry, entries>0);
  toolBar()->setItemEnabled(DeleteEntry, entries>0);
}

