<commit_before>/*
 *  editdlg.cpp  -  dialogue to create or modify an alarm
 *  Program:  kalarm
 *  (C) 2001 - 2004 by <NAME> <<EMAIL>>
 *
 *  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.
 *
 *  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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "kalarm.h"

#include <limits.h>

#include <qlayout.h>
#include <qpopupmenu.h>
#include <qvbox.h>
#include <qgroupbox.h>
#include <qwidgetstack.h>
#include <qdragobject.h>
#include <qlabel.h>
#include <qmessagebox.h>
#include <qvalidator.h>
#include <qwhatsthis.h>
#include <qtooltip.h>
#include <qdir.h>
#include <qstyle.h>

#include <kglobal.h>
#include <klocale.h>
#include <kconfig.h>
#include <kfiledialog.h>
#include <kiconloader.h>
#include <kio/netaccess.h>
#include <kfileitem.h>
#include <kmessagebox.h>
#include <kurldrag.h>
#include <kurlcompletion.h>
#include <kwinmodule.h>
#include <kstandarddirs.h>
#include <kstdguiitem.h>
#include <kabc/addresseedialog.h>
#include <kdebug.h>

#include <maillistdrag.h>

#include "kalarmapp.h"
#include "preferences.h"
#include "alarmtimewidget.h"
#include "soundpicker.h"
#include "reminder.h"
#include "recurrenceedit.h"
#include "colourcombo.h"
#include "fontcolourbutton.h"
#include "kamail.h"
#include "deferdlg.h"
#include "radiobutton.h"
#include "checkbox.h"
#include "combobox.h"
#include "spinbox.h"
#include "timespinbox.h"
#include "timeperiod.h"
#include "editdlg.moc"
#include "editdlgprivate.moc"

using namespace KCal;


EditAlarmDlg::EditAlarmDlg(const QString& caption, QWidget* parent, const char* name,
	                        const KAEvent* event, bool readOnly)
	: KDialogBase(KDialogBase::Tabbed, caption, (readOnly ? Cancel|Try : Ok|Cancel|Try),
	              (readOnly ? Cancel : Ok), parent, name),
	  mRecurPageShown(false),
	  mRecurSetDefaultEndDate(true),
	  mReminderDeferral(false),
	  mReminderArchived(false),
	  mEmailRemoveButton(0),
	  mDeferGroup(0),
	  mReadOnly(readOnly),
	  mSavedEvent(0)
{
	if (event  &&  event->action() == KAEvent::COMMAND  &&  theApp()->noShellAccess())
		mReadOnly = true;     // don't allow editing of existing command alarms in kiosk mode

	QVBox* mainPageBox = addVBoxPage(i18n("&Alarm"));
	mMainPageIndex = pageIndex(mainPageBox);
	PageFrame* mainPage = new PageFrame(mainPageBox);
	connect(mainPage, SIGNAL(shown()), SLOT(slotShowMainPage()));
	QVBoxLayout* topLayout = new QVBoxLayout(mainPage, marginKDE2, spacingHint());

	// Recurrence tab
	QVBox* recurPage = addVBoxPage(i18n("&Recurrence"));
	mRecurPageIndex = pageIndex(recurPage);
	mRecurrenceEdit = new RecurrenceEdit(readOnly, recurPage, "recurPage");
	connect(mRecurrenceEdit, SIGNAL(shown()), SLOT(slotShowRecurrenceEdit()));
	connect(mRecurrenceEdit, SIGNAL(typeChanged(int)), SLOT(slotRecurTypeChange(int)));
	connect(mRecurrenceEdit, SIGNAL(frequencyChanged()), SLOT(slotRecurFrequencyChange()));

	// Alarm action

	mActionGroup = new QButtonGroup(i18n("Action"), mainPage, "actionGroup");
	connect(mActionGroup, SIGNAL(clicked(int)), SLOT(slotAlarmTypeClicked(int)));
	topLayout->addWidget(mActionGroup, 1);
	QGridLayout* grid = new QGridLayout(mActionGroup, 3, 5, marginKDE2 + marginHint(), spacingHint());
	grid->addRowSpacing(0, fontMetrics().lineSpacing()/2);

	// Message radio button
	mMessageRadio = new RadioButton(i18n("Te&xt"), mActionGroup, "messageButton");
	mMessageRadio->setFixedSize(mMessageRadio->sizeHint());
	mMessageRadio->setReadOnly(mReadOnly);
	QWhatsThis::add(mMessageRadio,
	      i18n("If checked, the alarm will display a text message."));
	grid->addWidget(mMessageRadio, 1, 0);
	grid->setColStretch(1, 1);

	// File radio button
	mFileRadio = new RadioButton(i18n("&File"), mActionGroup, "fileButton");
	mFileRadio->setFixedSize(mFileRadio->sizeHint());
	mFileRadio->setReadOnly(mReadOnly);
	QWhatsThis::add(mFileRadio,
	      i18n("If checked, the alarm will display the contents of a text or image file."));
	grid->addWidget(mFileRadio, 1, 2);
	grid->setColStretch(3, 1);

	// Command radio button
	mCommandRadio = new RadioButton(i18n("Co&mmand"), mActionGroup, "cmdButton");
	mCommandRadio->setFixedSize(mCommandRadio->sizeHint());
	mCommandRadio->setReadOnly(mReadOnly);
	QWhatsThis::add(mCommandRadio,
	      i18n("If checked, the alarm will execute a shell command."));
	grid->addWidget(mCommandRadio, 1, 4);
	grid->setColStretch(5, 1);

	// Email radio button
	mEmailRadio = new RadioButton(i18n("&Email"), mActionGroup, "emailButton");
	mEmailRadio->setFixedSize(mEmailRadio->sizeHint());
	mEmailRadio->setReadOnly(mReadOnly);
	QWhatsThis::add(mEmailRadio,
	      i18n("If checked, the alarm will send an email."));
	grid->addWidget(mEmailRadio, 1, 6);

	initDisplayAlarms(mActionGroup);
	initCommand(mActionGroup);
	initEmail(mActionGroup);
	mAlarmTypeStack = new QWidgetStack(mActionGroup);
	grid->addMultiCellWidget(mAlarmTypeStack, 2, 2, 0, 6);
	grid->setRowStretch(2, 1);
	mAlarmTypeStack->addWidget(mDisplayAlarmsFrame, 0);
	mAlarmTypeStack->addWidget(mCommandFrame, 1);
	mAlarmTypeStack->addWidget(mEmailFrame, 1);

	bool recurs = event && event->recurs();
	if (recurs  &&  event->deferred())
	{
		// Deferred date/time for event without a time or recurring event
		mDeferGroup = new QGroupBox(1, Qt::Vertical, i18n("Deferred Alarm"), mainPage, "deferGroup");
		topLayout->addWidget(mDeferGroup);
		QLabel* label = new QLabel(i18n("Deferred to:"), mDeferGroup);
		label->setFixedSize(label->sizeHint());

		mDeferDateTime = event->deferDateTime();
		mDeferTimeLabel = new QLabel(mDeferDateTime.formatLocale(), mDeferGroup);

		if (!mReadOnly)
		{
			QPushButton* button = new QPushButton(i18n("C&hange..."), mDeferGroup);
			button->setFixedSize(button->sizeHint());
			connect(button, SIGNAL(clicked()), SLOT(slotEditDeferral()));
			QWhatsThis::add(button, i18n("Change the alarm's deferred time, or cancel the deferral"));
		}
		mDeferGroup->addSpace(0);
	}

	QBoxLayout* layout = new QHBoxLayout(topLayout);

	// Date and time entry
	mTimeWidget = new AlarmTimeWidget(i18n("Time"), AlarmTimeWidget::AT_TIME | AlarmTimeWidget::NARROW,
	                                  mainPage, "timeGroup");
	mTimeWidget->setReadOnly(mReadOnly);
	connect(mTimeWidget, SIGNAL(anyTimeToggled(bool)), SLOT(slotAnyTimeToggled(bool)));
	layout->addWidget(mTimeWidget);

	layout->addSpacing(marginHint() - spacingHint());
	QBoxLayout* vlayout = new QVBoxLayout(layout);
	layout->addStretch();

	// Repetition type radio buttons
	QHBox* box = new QHBox(mainPage);   // this is to control the QWhatsThis text display area
	box->setSpacing(KDialog::spacingHint());
	QLabel* label = new QLabel(i18n("Recurrence:"), box);
	label->setFixedSize(label->sizeHint());
	mRecurrenceText = new QLabel(box);
	QWhatsThis::add(box,
	      i18n("How often the alarm recurs.\nThe alarm's recurrence is configured in the Recurrence tab."));
	box->setFixedHeight(box->sizeHint().height());
	vlayout->addWidget(box, 0, Qt::AlignLeft);

	// Late display checkbox - default = allow late display
	mLateCancel = createLateCancelCheckbox(mReadOnly, mainPage);
	mLateCancel->setFixedSize(mLateCancel->sizeHint());
	vlayout->addWidget(mLateCancel, 0, Qt::AlignLeft);

	setButtonWhatsThis(Ok, i18n("Schedule the alarm at the specified time."));

	topLayout->activate();

	mDeferGroupHeight = mDeferGroup ? mDeferGroup->height() + spacingHint() : 0;
	QSize size = minimumSize();
	size.setHeight(size.height() - mDeferGroupHeight);
	mBasicSize = theApp()->readConfigWindowSize("EditDialog", size);
	resize(mBasicSize);

	// Set up initial values
	if (event)
	{
		// Set the values to those for the specified event
		if (event->defaultFont())
			mFontColourButton->setDefaultFont();
		else
			mFontColourButton->setFont(event->font());
		mFontColourButton->setBgColour(event->bgColour());
		mFontColourButton->setFgColour(event->fgColour());
		mBgColourChoose->setColour(event->bgColour());     // set colour before setting alarm type buttons
		mTimeWidget->setDateTime(!event->mainExpired() ? event->mainDateTime()
		                         : recurs ? DateTime() : event->deferDateTime());

		setAction(event->action(), event->cleanText());
		if (event->action() == KAEvent::EMAIL)
			mEmailAttachList->insertStringList(event->emailAttachments());

		mLateCancel->setChecked(event->lateCancel());
		mConfirmAck->setChecked(event->confirmAck());
		int reminder = event->reminder();
		if (!reminder  &&  event->reminderDeferral()  &&  !recurs)
		{
			reminder = event->reminderDeferral();
			mReminderDeferral = true;
		}
		if (!reminder  &&  event->reminderArchived()  &&  recurs)
		{
			reminder = event->reminderArchived();
			mReminderArchived = true;
		}
		mReminder->setMinutes(reminder, mTimeWidget->anyTime());
		mRecurrenceText->setText(event->recurrenceText());
		mRecurrenceEdit->set(*event);   // must be called after mTimeWidget is set up, to ensure correct date-only enabling
		mSoundPicker->setFile(event->audioFile());
		mSoundPicker->setChecked(event->beep() || !event->audioFile().isEmpty());
		mEmailToEdit->setText(event->emailAddresses(", "));
		mEmailSubjectEdit->setText(event->emailSubject());
		mEmailBcc->setChecked(event->emailBcc());
	}
	else
	{
		// Set the values to their defaults
		if (theApp()->noShellAccess())
			mCommandRadio->setEnabled(false);    // don't allow shell commands in kiosk mode
		Preferences* preferences = Preferences::instance();
		mFontColourButton->setDefaultFont();
		mFontColourButton->setBgColour(preferences->defaultBgColour());
		mFontColourButton->setFgColour(preferences->defaultFgColour());
		mBgColourChoose->setColour(preferences->defaultBgColour());     // set colour before setting alarm type buttons
		QDateTime defaultTime = QDateTime::currentDateTime().addSecs(60);
		mTimeWidget->setDateTime(defaultTime);
		mActionGroup->setButton(mActionGroup->id(mMessageRadio));
		mLateCancel->setChecked(preferences->defaultLateCancel());
		mConfirmAck->setChecked(preferences->defaultConfirmAck());
		mReminder->setMinutes(0, false);
		mRecurrenceEdit->setDefaults(defaultTime);   // must be called after mTimeWidget is set up, to ensure correct date-only enabling
		slotRecurFrequencyChange();      // update the Recurrence text
		mSoundPicker->setFile(preferences->defaultSoundFile());
		mSoundPicker->setChecked(preferences->defaultBeep() || !preferences->defaultSoundFile().isEmpty());
		mEmailBcc->setChecked(preferences->defaultEmailBcc());
	}

	size = mBasicSize;
	size.setHeight(size.height() + mDeferGroupHeight);
	resize(size);

	bool enable = !!mEmailAttachList->count();
	mEmailAttachList->setEnabled(enable);
	if (mEmailRemoveButton)
		mEmailRemoveButton->setEnabled(enable);

	// Save the initial state of all controls so that we can later tell if they have changed
	saveState(event);
}

EditAlarmDlg::~EditAlarmDlg()
{
	delete mSavedEvent;
}

/******************************************************************************
 * Set up the dialog controls common to display alarms.
 */
void EditAlarmDlg::initDisplayAlarms(QWidget* parent)
{
	mDisplayAlarmsFrame = new QFrame(parent);
	mDisplayAlarmsFrame->setFrameStyle(QFrame::NoFrame);
	QBoxLayout* frameLayout = new QVBoxLayout(mDisplayAlarmsFrame, 0, spacingHint());

	// Text message edit box
	mTextMessageEdit = new TextEdit(mDisplayAlarmsFrame);
	mTextMessageEdit->setWordWrap(QTextEdit::NoWrap);
	mTextMessageEdit->setReadOnly(mReadOnly);
	QWhatsThis::add(mTextMessageEdit, i18n("Enter the text of the alarm message. It may be multi-line."));
	frameLayout->addWidget(mTextMessageEdit);

	// File name edit box
	mFileBox = new QHBox(mDisplayAlarmsFrame);
	frameLayout->addWidget(mFileBox);
	mFileMessageEdit = new LineEdit(true, mFileBox);
	mFileMessageEdit->setReadOnly(mReadOnly);
	mFileMessageEdit->setAcceptDrops(true);
	QWhatsThis::add(mFileMessageEdit, i18n("Enter the name or URL of a text or image file to display."));

	// File browse button
	if (!mReadOnly)
	{
		QPushButton* button = new QPushButton(mFileBox);
		button->setPixmap(SmallIcon("fileopen"));
		button->setFixedSize(button->sizeHint());
		connect(button, SIGNAL(clicked()), SLOT(slotBrowseFile()));
		QWhatsThis::add(button, i18n("Select a text or image file to display."));
	}

	// Sound checkbox and file selector
	QBoxLayout* layout = new QHBoxLayout(frameLayout);
	mSoundPicker = new SoundPicker(mReadOnly, mDisplayAlarmsFrame);
	mSoundPicker->setFixedSize(mSoundPicker->sizeHint());
	layout->addWidget(mSoundPicker);
	layout->addSpacing(2*KDialog::spacingHint());
	layout->addStretch();

	// Colour choice drop-down list
	QHBox* box;
	mBgColourChoose = createBgColourChooser(mReadOnly, &box, mDisplayAlarmsFrame);
	mBgColourChoose->setFixedSize(mBgColourChoose->sizeHint());
	connect(mBgColourChoose, SIGNAL(highlighted(const QColor&)), SLOT(slotBgColourSelected(const QColor&)));
	layout->addWidget(box);

	// Acknowledgement confirmation required - default = no confirmation
	layout = new QHBoxLayout(frameLayout);
	mConfirmAck = createConfirmAckCheckbox(mReadOnly, mDisplayAlarmsFrame);
	mConfirmAck->setFixedSize(mConfirmAck->sizeHint());
	layout->addWidget(mConfirmAck);
	layout->addSpacing(2*KDialog::spacingHint());
	layout->addStretch();

	// Font and colour choice drop-down list
	mFontColourButton = new FontColourButton(mDisplayAlarmsFrame);
	mFontColourButton->setFixedSize(mFontColourButton->sizeHint());
	if (mReadOnly)
		mFontColourButton->hide();
	mFontColourButton->setReadOnly(mReadOnly);
	connect(mFontColourButton, SIGNAL(selected()), SLOT(slotFontColourSelected()));
	layout->addWidget(mFontColourButton);

	// Reminder
	static const QString reminderText = i18n("Enter how long in advance of the main alarm to display a reminder alarm.");
	mReminder = new Reminder(i18n("Rem&inder:"),
	                         i18n("Check to additionally display a reminder in advance of the main alarm time(s)."),
	                         QString("%1\n\n%2").arg(reminderText).arg(TimeSpinBox::shiftWhatsThis()),
	                         true, mDisplayAlarmsFrame);
	mReminder->setFixedSize(mReminder->sizeHint());
	mReminder->setReadOnly(mReadOnly);
	frameLayout->addWidget(mReminder, 0, Qt::AlignLeft);

	// Top-adjust the controls
	mFilePadding = new QHBox(mDisplayAlarmsFrame);
	frameLayout->addWidget(mFilePadding);
	frameLayout->setStretchFactor(mFilePadding, 1);
}

/******************************************************************************
 * Set up the command alarm dialog controls.
 */
void EditAlarmDlg::initCommand(QWidget* parent)
{
	mCommandFrame = new QFrame(parent);
	mCommandFrame->setFrameStyle(QFrame::NoFrame);
	QBoxLayout* layout = new QVBoxLayout(mCommandFrame);

	mCommandMessageEdit = new LineEdit(true, mCommandFrame);
	mCommandMessageEdit->setReadOnly(mReadOnly);
	QWhatsThis::add(mCommandMessageEdit, i18n("Enter a shell command to execute."));
	layout->addWidget(mCommandMessageEdit);
	layout->addStretch();
}

/******************************************************************************
 * Set up the email alarm dialog controls.
 */
void EditAlarmDlg::initEmail(QWidget* parent)
{
	mEmailFrame = new QFrame(parent);
	mEmailFrame->setFrameStyle(QFrame::NoFrame);
	QBoxLayout* layout = new QVBoxLayout(mEmailFrame, 0, spacingHint());
	QGridLayout* grid = new QGridLayout(layout, 2, 3, spacingHint());
	grid->setColStretch(1, 1);

	// Email recipients
	QLabel* label = new QLabel(i18n("Email addressee", "To:"), mEmailFrame);
	label->setFixedSize(label->sizeHint());
	grid->addWidget(label, 0, 0);

	mEmailToEdit = new LineEdit(false, mEmailFrame);
	mEmailToEdit->setMinimumSize(mEmailToEdit->sizeHint());
	mEmailToEdit->setReadOnly(mReadOnly);
	QWhatsThis::add(mEmailToEdit,
	      i18n("Enter the addresses of the email recipients. Separate multiple addresses by "
	           "commas or semicolons."));
	grid->addWidget(mEmailToEdit, 0, 1);

	if (!mReadOnly)
	{
		QPushButton* button = new QPushButton(mEmailFrame);
		button->setPixmap(SmallIcon("contents"));
		button->setFixedSize(button->sizeHint());
		connect(button, SIGNAL(clicked()), SLOT(openAddressBook()));
		QToolTip::add(button, i18n("Open address book"));
		QWhatsThis::add(button, i18n("Select email addresses from your address book."));
		grid->addWidget(button, 0, 2);
	}

	// Email subject
	label = new QLabel(i18n("Email subject", "Sub&ject:"), mEmailFrame);
	label->setFixedSize(label->sizeHint());
	grid->addWidget(label, 1, 0);

	mEmailSubjectEdit = new LineEdit(false, mEmailFrame);
	mEmailSubjectEdit->setMinimumSize(mEmailSubjectEdit->sizeHint());
	mEmailSubjectEdit->setReadOnly(mReadOnly);
	label->setBuddy(mEmailSubjectEdit);
	QWhatsThis::add(mEmailSubjectEdit, i18n("Enter the email subject."));
	grid->addMultiCellWidget(mEmailSubjectEdit, 1, 1, 1, 2);

	// Email body
	mEmailMessageEdit = new TextEdit(mEmailFrame);
	mEmailMessageEdit->setReadOnly(mReadOnly);
	QWhatsThis::add(mEmailMessageEdit, i18n("Enter the email message."));
	layout->addWidget(mEmailMessageEdit);

	// Email attachments
	grid = new QGridLayout(layout, 2, 3, spacingHint());
	label = new QLabel(i18n("Attachment&s:"), mEmailFrame);
	label->setFixedSize(label->sizeHint());
	grid->addWidget(label, 0, 0);

	mEmailAttachList = new QComboBox(true, mEmailFrame);
	mEmailAttachList->setMinimumSize(mEmailAttachList->sizeHint());
	mEmailAttachList->lineEdit()->setReadOnly(true);
QListBox* list = mEmailAttachList->listBox();
QRect rect = list->geometry();
list->setGeometry(rect.left() - 50, rect.top(), rect.width(), rect.height());
	label->setBuddy(mEmailAttachList);
	QWhatsThis::add(mEmailAttachList,
	      i18n("Files to send as attachments to the email."));
	grid->addWidget(mEmailAttachList, 0, 1);
	grid->setColStretch(1, 1);

	if (!mReadOnly)
	{
		QPushButton* button = new QPushButton(i18n("Add..."), mEmailFrame);
		connect(button, SIGNAL(clicked()), SLOT(slotAddAttachment()));
		QWhatsThis::add(button, i18n("Add an attachment to the email."));
		grid->addWidget(button, 0, 2);

		mEmailRemoveButton = new QPushButton(i18n("Remo&ve"), mEmailFrame);
		connect(mEmailRemoveButton, SIGNAL(clicked()), SLOT(slotRemoveAttachment()));
		QWhatsThis::add(mEmailRemoveButton, i18n("Remove the highlighted attachment from the email."));
		grid->addWidget(mEmailRemoveButton, 1, 2);
	}

	// BCC email to sender
	mEmailBcc = new CheckBox(i18n("Copy email to &self"), mEmailFrame);
	mEmailBcc->setFixedSize(mEmailBcc->sizeHint());
	mEmailBcc->setReadOnly(mReadOnly);
	QWhatsThis::add(mEmailBcc,
	      i18n("If checked, the email will be blind copied to you."));
	grid->addMultiCellWidget(mEmailBcc, 1, 1, 0, 1, Qt::AlignLeft);
}

/******************************************************************************
 * Set the dialog's action and the action's text.
 */
void EditAlarmDlg::setAction(KAEvent::Action action, const QString& text)
{
	QRadioButton* radio;
	switch (action)
	{
		case KAEvent::FILE:
			radio = mFileRadio;
			mFileMessageEdit->setText(text);
			break;
		case KAEvent::COMMAND:
			radio = mCommandRadio;
			mCommandMessageEdit->setText(text);
			break;
		case KAEvent::EMAIL:
			radio = mEmailRadio;
			mEmailMessageEdit->setText(text);
			break;
		case KAEvent::MESSAGE:
		default:
			radio = mMessageRadio;
			mTextMessageEdit->setText(text);
			break;
	}
	mActionGroup->setButton(mActionGroup->id(radio));
}

/******************************************************************************
 * Create a widget to choose the alarm message background colour.
 */
ColourCombo* EditAlarmDlg::createBgColourChooser(bool readOnly, QHBox** box, QWidget* parent, const char* name)
{
	*box = new QHBox(parent);   // this is to control the QWhatsThis text display area
	QLabel* label = new QLabel(i18n("&Background color:"), *box);
	label->setFixedSize(label->sizeHint());
	ColourCombo* widget = new ColourCombo(*box, name);
	QSize size = widget->sizeHint();
	widget->setMinimumHeight(size.height() + 4);
	widget->setReadOnly(readOnly);
	QToolTip::add(widget, i18n("Message color"));
	label->setBuddy(widget);
	(*box)->setFixedHeight((*box)->sizeHint().height());
	QWhatsThis::add(*box, i18n("Choose the background color for the alarm message."));
	return widget;
}

/******************************************************************************
 * Create an "acknowledgement confirmation required" checkbox.
 */
CheckBox* EditAlarmDlg::createConfirmAckCheckbox(bool readOnly, QWidget* parent, const char* name)
{
	CheckBox* widget = new CheckBox(i18n("Confirm ac&knowledgement"), parent, name);
	widget->setReadOnly(readOnly);
	QWhatsThis::add(widget,
	      i18n("Check to be prompted for confirmation when you acknowledge the alarm."));
	return widget;
}

/******************************************************************************
 * Create an "cancel if late" checkbox.
 */
CheckBox* EditAlarmDlg::createLateCancelCheckbox(bool readOnly, QWidget* parent, const char* name)
{
	CheckBox* widget = new CheckBox(i18n("Ca&ncel if late"), parent, name);
	widget->setReadOnly(readOnly);
	QWhatsThis::add(widget,
	      i18n("If checked, the alarm will be canceled if it cannot be triggered within 1 "
	           "minute of the specified time. Possible reasons for not triggering include your "
	           "being logged off, X not running, or the alarm daemon not running.\n\n"
	           "If unchecked, the alarm will be triggered at the first opportunity after "
	           "the specified time, regardless of how late it is."));
	return widget;
}

/******************************************************************************
 * Save the state of all controls.
 */
void EditAlarmDlg::saveState(const KAEvent* event)
{
	mSavedEvent          = 0;
	if (event)
		mSavedEvent = new KAEvent(*event);
	mSavedTypeRadio      = mActionGroup->selected();
	mSavedBeep           = mSoundPicker->beep();
	mSavedSoundFile      = mSoundPicker->file();
	mSavedConfirmAck     = mConfirmAck->isChecked();
	mSavedFont           = mFontColourButton->font();
	mSavedFgColour       = mFontColourButton->fgColour();
	mSavedBgColour       = mBgColourChoose->color();
	mSavedReminder       = mReminder->getMinutes();
	checkText(mSavedTextFileCommandMessage, false);
	mSavedEmailTo        = mEmailToEdit->text();
	mSavedEmailSubject   = mEmailSubjectEdit->text();
	mSavedEmailAttach.clear();
	for (int i = 0;  i < mEmailAttachList->count();  ++i)
		mSavedEmailAttach += mEmailAttachList->text(i);
	mSavedEmailBcc       = mEmailBcc->isChecked();
	mSavedDateTime       = mTimeWidget->getDateTime(false, false);
	mSavedLateCancel     = mLateCancel->isChecked();
	mSavedRecurrenceType = mRecurrenceEdit->repeatType();
}

/******************************************************************************
 * Check whether any of the controls other than the deferral time has changed
 * state since the dialog was first displayed.
 */
bool EditAlarmDlg::stateChanged() const
{
	QString textFileCommandMessage;
	checkText(textFileCommandMessage, false);
	if (mSavedTypeRadio        != mActionGroup->selected()
	||  mSavedDateTime         != mTimeWidget->getDateTime(false, false)
	||  mSavedLateCancel       != mLateCancel->isChecked()
	||  textFileCommandMessage != mSavedTextFileCommandMessage
	||  mSavedRecurrenceType   != mRecurrenceEdit->repeatType())
		return true;
	if (mMessageRadio->isOn()  ||  mFileRadio->isOn())
	{
		if (mSavedBeep       != mSoundPicker->beep()
		||  mSavedSoundFile  != mSoundPicker->file()
		||  mSavedConfirmAck != mConfirmAck->isChecked()
		||  mSavedFont       != mFontColourButton->font()
		||  mSavedFgColour   != mFontColourButton->fgColour()
		||  mSavedBgColour   != mBgColourChoose->color()
		||  mSavedReminder   != mReminder->getMinutes())
			return true;
	}
	else if (mEmailRadio->isOn())
	{
		QStringList emailAttach;
		for (int i = 0;  i < mEmailAttachList->count();  ++i)
			emailAttach += mEmailAttachList->text(i);
		if (mSavedEmailTo      != mEmailToEdit->text()
		||  mSavedEmailSubject != mEmailSubjectEdit->text()
		||  mSavedEmailAttach  != emailAttach
		||  mSavedEmailBcc     != mEmailBcc->isChecked())
			return true;
	}
	if (mSavedEvent->recurType() != KAEvent::NO_RECUR)
	{
		if (!mSavedEvent->recurrence())
			return true;
		KAEvent event;
		event.setTime(mSavedEvent->startDateTime().dateTime());
		mRecurrenceEdit->updateEvent(event);
		if (!event.recurrence())
			return true;
		event.recurrence()->setFloats(mSavedEvent->startDateTime().isDateOnly());
		if (*event.recurrence() != *mSavedEvent->recurrence()
		||  event.exceptionDates() != mSavedEvent->exceptionDates()
		||  event.exceptionDateTimes() != mSavedEvent->exceptionDateTimes())
			return true;
	}
	return false;
}

/******************************************************************************
 * Get the currently entered message data.
 * The data is returned in the supplied Event instance.
 */
void EditAlarmDlg::getEvent(KAEvent& event)
{
	if (!mSavedEvent  ||  stateChanged())
	{
		// It's a new event, or the edit controls have changed
		event.set(mAlarmDateTime.dateTime(), mAlarmMessage, mBgColourChoose->color(),
		          mFontColourButton->fgColour(), mFontColourButton->font(), getAlarmType(),
		          getAlarmFlags());
		event.setAudioFile(mSoundPicker->file());
		event.setEmail(mEmailAddresses, mEmailSubjectEdit->text(), mEmailAttachments);
		event.setReminder(mReminder->getMinutes());
		if (mRecurrenceEdit->repeatType() != RecurrenceEdit::NO_RECUR)
		{
			mRecurrenceEdit->updateEvent(event);
			mAlarmDateTime = event.startDateTime();
			if (mDeferDateTime.isValid()  &&  mDeferDateTime < mAlarmDateTime)
			{
				bool deferReminder = false;
				int reminder = mReminder->getMinutes();
				if (reminder)
				{
					DateTime remindTime = mAlarmDateTime.addMins(-reminder);
					if (mDeferDateTime > remindTime)
						deferReminder = true;
				}
				event.defer(mDeferDateTime, deferReminder, false);
			}
		}
	}
	else
	{
		// Only the deferral time may have changed
		event = *mSavedEvent;
		if (mSavedEvent->deferred())
		{
			if (mDeferDateTime.isValid())
				event.defer(mDeferDateTime, event.reminderDeferral(), false);
			else
				event.cancelDefer();
		}
	}
}

/******************************************************************************
 * Get the currently specified alarm flag bits.
 */
int EditAlarmDlg::getAlarmFlags() const
{
	bool displayAlarm = mMessageRadio->isOn() || mFileRadio->isOn();
	return (displayAlarm && mSoundPicker->beep()          ? KAEvent::BEEP : 0)
	     | (mLateCancel->isChecked()                      ? KAEvent::LATE_CANCEL : 0)
	     | (displayAlarm && mConfirmAck->isChecked()      ? KAEvent::CONFIRM_ACK : 0)
	     | (mEmailRadio->isOn() && mEmailBcc->isChecked() ? KAEvent::EMAIL_BCC : 0)
	     | (mRecurrenceEdit->repeatType() == RecurrenceEdit::AT_LOGIN ? KAEvent::REPEAT_AT_LOGIN : 0)
	     | (mAlarmDateTime.isDateOnly()                   ? KAEvent::ANY_TIME : 0)
	     | (mFontColourButton->defaultFont()              ? KAEvent::DEFAULT_FONT : 0);
}

/******************************************************************************
 * Get the currently selected alarm type.
 */
KAEvent::Action EditAlarmDlg::getAlarmType() const
{
	return mFileRadio->isOn()    ? KAEvent::FILE
	     : mCommandRadio->isOn() ? KAEvent::COMMAND
	     : mEmailRadio->isOn()   ? KAEvent::EMAIL
	     :                         KAEvent::MESSAGE;
}

/******************************************************************************
*  Called when the dialog's size has changed.
*  Records the new size (adjusted to ignore the optional height of the deferred
*  time edit widget) in the config file.
*/
void EditAlarmDlg::resizeEvent(QResizeEvent* re)
{
	if (isVisible())
	{
		mBasicSize = re->size();
		mBasicSize.setHeight(mBasicSize.height() - mDeferGroupHeight);
		theApp()->writeConfigWindowSize("EditDialog", mBasicSize);
	}
	KDialog::resizeEvent(re);
}

/******************************************************************************
*  Called when the OK button is clicked.
*  Set up the new alarm.
*/
void EditAlarmDlg::slotOk()
{
	if (activePageIndex() == mRecurPageIndex  &&  mRecurrenceEdit->repeatType() == RecurrenceEdit::AT_LOGIN)
		mTimeWidget->setDateTime(mRecurrenceEdit->endDateTime());
	QWidget* errWidget;
	bool timedRecurrence = mRecurrenceEdit->isTimedRepeatType();
	mAlarmDateTime = mTimeWidget->getDateTime(!timedRecurrence, false, &errWidget);
	if (errWidget)
	{
		showPage(mMainPageIndex);
		errWidget->setFocus();
		mTimeWidget->getDateTime();   // display the error message now
	}
	else if (checkEmailData())
	{
		if (timedRecurrence)
		{
			QDateTime now = QDateTime::currentDateTime();
			if (mAlarmDateTime.date() < now.date()
			||  !mAlarmDateTime.isDateOnly() && mAlarmDateTime.time() < now.time())
			{
				// A timed recurrence has an entered start date which
				// has already expired, so we must adjust it.
				KAEvent event;
				getEvent(event);
				if (event.nextOccurrence(now, mAlarmDateTime) == KAEvent::NO_OCCURRENCE)
				{
					KMessageBox::sorry(this, i18n("Recurrence has already expired"));
					return;
				}
			}
		}
		QString errmsg;<commit_msg>Cancel deferral if reminder is set before it, to prevent it becoming stuck<commit_after>/*
 *  editdlg.cpp  -  dialogue to create or modify an alarm
 *  Program:  kalarm
 *  (C) 2001 - 2004 by <NAME> <<EMAIL>>
 *
 *  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.
 *
 *  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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of this program with any edition of the Qt library by
 *  Trolltech AS, Norway (or with modified versions of Qt that use the same
 *  license as Qt), and distribute linked combinations including the two.
 *  You must obey the GNU General Public License in all respects for all of
 *  the code used other than Qt.  If you modify this file, you may extend
 *  this exception to your version of the file, but you are not obligated to
 *  do so. If you do not wish to do so, delete this exception statement from
 *  your version.
 */

#include "kalarm.h"

#include <limits.h>

#include <qlayout.h>
#include <qpopupmenu.h>
#include <qvbox.h>
#include <qgroupbox.h>
#include <qwidgetstack.h>
#include <qdragobject.h>
#include <qlabel.h>
#include <qmessagebox.h>
#include <qvalidator.h>
#include <qwhatsthis.h>
#include <qtooltip.h>
#include <qdir.h>
#include <qstyle.h>

#include <kglobal.h>
#include <klocale.h>
#include <kconfig.h>
#include <kfiledialog.h>
#include <kiconloader.h>
#include <kio/netaccess.h>
#include <kfileitem.h>
#include <kmessagebox.h>
#include <kurldrag.h>
#include <kurlcompletion.h>
#include <kwinmodule.h>
#include <kstandarddirs.h>
#include <kstdguiitem.h>
#include <kabc/addresseedialog.h>
#include <kdebug.h>

#include <maillistdrag.h>

#include "alarmtimewidget.h"
#include "checkbox.h"
#include "colourcombo.h"
#include "combobox.h"
#include "deferdlg.h"
#include "fontcolourbutton.h"
#include "kalarmapp.h"
#include "kamail.h"
#include "preferences.h"
#include "radiobutton.h"
#include "recurrenceedit.h"
#include "reminder.h"
#include "soundpicker.h"
#include "spinbox.h"
#include "timeperiod.h"
#include "timespinbox.h"
#include "editdlg.moc"
#include "editdlgprivate.moc"

using namespace KCal;


EditAlarmDlg::EditAlarmDlg(const QString& caption, QWidget* parent, const char* name,
	                        const KAEvent* event, bool readOnly)
	: KDialogBase(KDialogBase::Tabbed, caption, (readOnly ? Cancel|Try : Ok|Cancel|Try),
	              (readOnly ? Cancel : Ok), parent, name),
	  mRecurPageShown(false),
	  mRecurSetDefaultEndDate(true),
	  mReminderDeferral(false),
	  mReminderArchived(false),
	  mEmailRemoveButton(0),
	  mDeferGroup(0),
	  mReadOnly(readOnly),
	  mSavedEvent(0)
{
	if (event  &&  event->action() == KAEvent::COMMAND  &&  theApp()->noShellAccess())
		mReadOnly = true;     // don't allow editing of existing command alarms in kiosk mode
#if 0
	setButtonText(Default, i18n("Load Template..."));
	if (mTemplate)
	{
		QHBox* box = new QHBox(mainWidget);
		box->setSpacing(spacingHint());
		QLabel* label = new QLabel(i18n("Template name:"), box);
		label->setFixedSize(label->sizeHint());
		mTemplateName = new QLineEdit(box);
		mTemplateName->setReadOnly(mReadOnly);
		label->setBuddy(mTemplateName);
		QWhatsThis::add(box, i18n("Enter the name of the alarm template"));
		box->setFixedHeight(box->sizeHint().height());
	}
#endif

	QVBox* mainPageBox = addVBoxPage(i18n("&Alarm"));
	mMainPageIndex = pageIndex(mainPageBox);
	PageFrame* mainPage = new PageFrame(mainPageBox);
	connect(mainPage, SIGNAL(shown()), SLOT(slotShowMainPage()));
	QVBoxLayout* topLayout = new QVBoxLayout(mainPage, marginKDE2, spacingHint());

	// Recurrence tab
	QVBox* recurPage = addVBoxPage(i18n("&Recurrence"));
	mRecurPageIndex = pageIndex(recurPage);
	mRecurrenceEdit = new RecurrenceEdit(readOnly, recurPage, "recurPage");
	connect(mRecurrenceEdit, SIGNAL(shown()), SLOT(slotShowRecurrenceEdit()));
	connect(mRecurrenceEdit, SIGNAL(typeChanged(int)), SLOT(slotRecurTypeChange(int)));
	connect(mRecurrenceEdit, SIGNAL(frequencyChanged()), SLOT(slotRecurFrequencyChange()));

	// Alarm action

	mActionGroup = new QButtonGroup(i18n("Action"), mainPage, "actionGroup");
	connect(mActionGroup, SIGNAL(clicked(int)), SLOT(slotAlarmTypeClicked(int)));
	topLayout->addWidget(mActionGroup, 1);
	QGridLayout* grid = new QGridLayout(mActionGroup, 3, 5, marginKDE2 + marginHint(), spacingHint());
	grid->addRowSpacing(0, fontMetrics().lineSpacing()/2);

	// Message radio button
	mMessageRadio = new RadioButton(i18n("Te&xt"), mActionGroup, "messageButton");
	mMessageRadio->setFixedSize(mMessageRadio->sizeHint());
	mMessageRadio->setReadOnly(mReadOnly);
	QWhatsThis::add(mMessageRadio,
	      i18n("If checked, the alarm will display a text message."));
	grid->addWidget(mMessageRadio, 1, 0);
	grid->setColStretch(1, 1);

	// File radio button
	mFileRadio = new RadioButton(i18n("&File"), mActionGroup, "fileButton");
	mFileRadio->setFixedSize(mFileRadio->sizeHint());
	mFileRadio->setReadOnly(mReadOnly);
	QWhatsThis::add(mFileRadio,
	      i18n("If checked, the alarm will display the contents of a text or image file."));
	grid->addWidget(mFileRadio, 1, 2);
	grid->setColStretch(3, 1);

	// Command radio button
	mCommandRadio = new RadioButton(i18n("Co&mmand"), mActionGroup, "cmdButton");
	mCommandRadio->setFixedSize(mCommandRadio->sizeHint());
	mCommandRadio->setReadOnly(mReadOnly);
	QWhatsThis::add(mCommandRadio,
	      i18n("If checked, the alarm will execute a shell command."));
	grid->addWidget(mCommandRadio, 1, 4);
	grid->setColStretch(5, 1);

	// Email radio button
	mEmailRadio = new RadioButton(i18n("&Email"), mActionGroup, "emailButton");
	mEmailRadio->setFixedSize(mEmailRadio->sizeHint());
	mEmailRadio->setReadOnly(mReadOnly);
	QWhatsThis::add(mEmailRadio,
	      i18n("If checked, the alarm will send an email."));
	grid->addWidget(mEmailRadio, 1, 6);

	initDisplayAlarms(mActionGroup);
	initCommand(mActionGroup);
	initEmail(mActionGroup);
	mAlarmTypeStack = new QWidgetStack(mActionGroup);
	grid->addMultiCellWidget(mAlarmTypeStack, 2, 2, 0, 6);
	grid->setRowStretch(2, 1);
	mAlarmTypeStack->addWidget(mDisplayAlarmsFrame, 0);
	mAlarmTypeStack->addWidget(mCommandFrame, 1);
	mAlarmTypeStack->addWidget(mEmailFrame, 1);

	bool recurs = event && event->recurs();
	if (recurs  &&  event->deferred())
	{
		// Deferred date/time for event without a time or recurring event
		mDeferGroup = new QGroupBox(1, Qt::Vertical, i18n("Deferred Alarm"), mainPage, "deferGroup");
		topLayout->addWidget(mDeferGroup);
		QLabel* label = new QLabel(i18n("Deferred to:"), mDeferGroup);
		label->setFixedSize(label->sizeHint());

		mDeferDateTime = event->deferDateTime();
		mDeferTimeLabel = new QLabel(mDeferDateTime.formatLocale(), mDeferGroup);

		if (!mReadOnly)
		{
			QPushButton* button = new QPushButton(i18n("C&hange..."), mDeferGroup);
			button->setFixedSize(button->sizeHint());
			connect(button, SIGNAL(clicked()), SLOT(slotEditDeferral()));
			QWhatsThis::add(button, i18n("Change the alarm's deferred time, or cancel the deferral"));
		}
		mDeferGroup->addSpace(0);
	}

	QBoxLayout* layout = new QHBoxLayout(topLayout);

	// Date and time entry
#if 0
	if (mTemplate)
	{
		mTemplateTimeGroup = new ButtonGroup(i18n("Time"), mainPage, "templateGroup");
		connect(mTemplateTimeGroup, SIGNAL(buttonSet(int)), SLOT(slotTemplateTimeType(int)));
		layout->addWidget(mTemplateTimeGroup);
		QBoxLayout* vlayout = new QVBoxLayout(mTemplateTimeGroup, marginKDE2 + marginHint(), spacingHint());
		vlayout->addSpacing(fontMetrics().lineSpacing()/2);

		mTemplateDefaultTime = new RadioButton(i18n("&Default time"), mTemplateTimeGroup, "templateDefTimeButton");
		mTemplateDefaultTime->setFixedSize(mTemplateDefaultTime->sizeHint());
		mTemplateDefaultTime->setReadOnly(mReadOnly);
		QWhatsThis::add(mTemplateDefaultTime,
		      i18n("Do not specify a start time for alarms based on this template. "
		           "The normal default start time will be used."));
		vlayout->addWidget(mTemplateDefaultTime);

		QHBox* box = new QHBox(mTemplateTimeGroup);
		box->setSpacing(spacingHint());
		vlayout->addWidget(box);
		mTemplateUseTime = new RadioButton(i18n("Time:"), box, "templateTimeButton");
		mTemplateUseTime->setFixedSize(mTemplateUseTime->sizeHint());
		mTemplateUseTime->setReadOnly(mReadOnly);
		QWhatsThis::add(mTemplateUseTime,
		      i18n("Specify a start time for alarms based on this template."));
		mTemplateTimeGroup->insert(mTemplateUseTime);
		mTemplateTime = new TimeSpinBox(box, "templateTimeEdit");
		mTemplateTime->setValue(1439);
		mTemplateTime->setFixedSize(mTemplateTime->sizeHint());
		mTemplateTime->setReadOnly(mReadOnly);
		QWhatsThis::add(mTemplateTime,
		      QString("%1\n\n%2").arg(i18n("Enter the start time for alarms based on this template."))
		                         .arg(TimeSpinBox::shiftWhatsThis()));
		box->setStretchFactor(new QWidget(box), 1);    // left adjust the controls
		box->setFixedHeight(box->sizeHint().height());

		mTemplateAnyTime = new RadioButton(i18n("An&y time"), mTemplateTimeGroup, "templateAnyTimeButton");
		mTemplateAnyTime->setFixedSize(mTemplateAnyTime->sizeHint());
		mTemplateAnyTime->setReadOnly(mReadOnly);
		QWhatsThis::add(mTemplateAnyTime,
		      i18n("Set the '%1' option for alarms based on this template.").arg(i18n("Any time")));
		vlayout->addWidget(mTemplateAnyTime);
	}
	else
#endif
	mTimeWidget = new AlarmTimeWidget(i18n("Time"), AlarmTimeWidget::AT_TIME | AlarmTimeWidget::NARROW,
	                                  mainPage, "timeGroup");
	mTimeWidget->setReadOnly(mReadOnly);
	connect(mTimeWidget, SIGNAL(anyTimeToggled(bool)), SLOT(slotAnyTimeToggled(bool)));
	layout->addWidget(mTimeWidget);

	layout->addSpacing(marginHint() - spacingHint());
	QBoxLayout* vlayout = new QVBoxLayout(layout);
	layout->addStretch();

	// Repetition type radio buttons
	QHBox* box = new QHBox(mainPage);   // this is to control the QWhatsThis text display area
	box->setSpacing(KDialog::spacingHint());
	QLabel* label = new QLabel(i18n("Recurrence:"), box);
	label->setFixedSize(label->sizeHint());
	mRecurrenceText = new QLabel(box);
	QWhatsThis::add(box,
	      i18n("How often the alarm recurs.\nThe alarm's recurrence is configured in the Recurrence tab."));
	box->setFixedHeight(box->sizeHint().height());
	vlayout->addWidget(box, 0, Qt::AlignLeft);

	// Late display checkbox - default = allow late display
	mLateCancel = createLateCancelCheckbox(mReadOnly, mainPage);
	mLateCancel->setFixedSize(mLateCancel->sizeHint());
	vlayout->addWidget(mLateCancel, 0, Qt::AlignLeft);

	setButtonWhatsThis(Ok, i18n("Schedule the alarm at the specified time."));

	topLayout->activate();

	mDeferGroupHeight = mDeferGroup ? mDeferGroup->height() + spacingHint() : 0;
	QSize size = minimumSize();
	size.setHeight(size.height() - mDeferGroupHeight);
	mBasicSize = theApp()->readConfigWindowSize("EditDialog", size);
	resize(mBasicSize);

	// Set up initial values
	if (event)
	{
		// Set the values to those for the specified event
		if (event->defaultFont())
			mFontColourButton->setDefaultFont();
		else
			mFontColourButton->setFont(event->font());
		mFontColourButton->setBgColour(event->bgColour());
		mFontColourButton->setFgColour(event->fgColour());
		mBgColourChoose->setColour(event->bgColour());     // set colour before setting alarm type buttons
		mTimeWidget->setDateTime(!event->mainExpired() ? event->mainDateTime()
		                         : recurs ? DateTime() : event->deferDateTime());

		setAction(event->action(), event->cleanText());
		if (event->action() == KAEvent::EMAIL)
			mEmailAttachList->insertStringList(event->emailAttachments());

		mLateCancel->setChecked(event->lateCancel());
		mConfirmAck->setChecked(event->confirmAck());
		int reminder = event->reminder();
		if (!reminder  &&  event->reminderDeferral()  &&  !recurs)
		{
			reminder = event->reminderDeferral();
			mReminderDeferral = true;
		}
		if (!reminder  &&  event->reminderArchived()  &&  recurs)
		{
			reminder = event->reminderArchived();
			mReminderArchived = true;
		}
		mReminder->setMinutes(reminder, mTimeWidget->anyTime());
		mRecurrenceText->setText(event->recurrenceText());
		mRecurrenceEdit->set(*event);   // must be called after mTimeWidget is set up, to ensure correct date-only enabling
		mSoundPicker->setFile(event->audioFile());
		mSoundPicker->setChecked(event->beep() || !event->audioFile().isEmpty());
		mEmailToEdit->setText(event->emailAddresses(", "));
		mEmailSubjectEdit->setText(event->emailSubject());
		mEmailBcc->setChecked(event->emailBcc());
	}
	else
	{
		// Set the values to their defaults
		if (theApp()->noShellAccess())
			mCommandRadio->setEnabled(false);    // don't allow shell commands in kiosk mode
		Preferences* preferences = Preferences::instance();
		mFontColourButton->setDefaultFont();
		mFontColourButton->setBgColour(preferences->defaultBgColour());
		mFontColourButton->setFgColour(preferences->defaultFgColour());
		mBgColourChoose->setColour(preferences->defaultBgColour());     // set colour before setting alarm type buttons
		QDateTime defaultTime = QDateTime::currentDateTime().addSecs(60);
		mTimeWidget->setDateTime(defaultTime);
		mActionGroup->setButton(mActionGroup->id(mMessageRadio));
		mLateCancel->setChecked(preferences->defaultLateCancel());
		mConfirmAck->setChecked(preferences->defaultConfirmAck());
		mReminder->setMinutes(0, false);
		mRecurrenceEdit->setDefaults(defaultTime);   // must be called after mTimeWidget is set up, to ensure correct date-only enabling
		slotRecurFrequencyChange();      // update the Recurrence text
		mSoundPicker->setFile(preferences->defaultSoundFile());
		mSoundPicker->setChecked(preferences->defaultBeep() || !preferences->defaultSoundFile().isEmpty());
		mEmailBcc->setChecked(preferences->defaultEmailBcc());
	}

	size = mBasicSize;
	size.setHeight(size.height() + mDeferGroupHeight);
	resize(size);

	bool enable = !!mEmailAttachList->count();
	mEmailAttachList->setEnabled(enable);
	if (mEmailRemoveButton)
		mEmailRemoveButton->setEnabled(enable);

	// Save the initial state of all controls so that we can later tell if they have changed
	saveState(event);
}

EditAlarmDlg::~EditAlarmDlg()
{
	delete mSavedEvent;
}

/******************************************************************************
 * Set up the dialog controls common to display alarms.
 */
void EditAlarmDlg::initDisplayAlarms(QWidget* parent)
{
	mDisplayAlarmsFrame = new QFrame(parent);
	mDisplayAlarmsFrame->setFrameStyle(QFrame::NoFrame);
	QBoxLayout* frameLayout = new QVBoxLayout(mDisplayAlarmsFrame, 0, spacingHint());

	// Text message edit box
	mTextMessageEdit = new TextEdit(mDisplayAlarmsFrame);
	mTextMessageEdit->setWordWrap(QTextEdit::NoWrap);
	mTextMessageEdit->setReadOnly(mReadOnly);
	QWhatsThis::add(mTextMessageEdit, i18n("Enter the text of the alarm message. It may be multi-line."));
	frameLayout->addWidget(mTextMessageEdit);

	// File name edit box
	mFileBox = new QHBox(mDisplayAlarmsFrame);
	frameLayout->addWidget(mFileBox);
	mFileMessageEdit = new LineEdit(true, mFileBox);
	mFileMessageEdit->setReadOnly(mReadOnly);
	mFileMessageEdit->setAcceptDrops(true);
	QWhatsThis::add(mFileMessageEdit, i18n("Enter the name or URL of a text or image file to display."));

	// File browse button
	if (!mReadOnly)
	{
		QPushButton* button = new QPushButton(mFileBox);
		button->setPixmap(SmallIcon("fileopen"));
		button->setFixedSize(button->sizeHint());
		connect(button, SIGNAL(clicked()), SLOT(slotBrowseFile()));
		QWhatsThis::add(button, i18n("Select a text or image file to display."));
	}

	// Sound checkbox and file selector
	QBoxLayout* layout = new QHBoxLayout(frameLayout);
	mSoundPicker = new SoundPicker(mReadOnly, mDisplayAlarmsFrame);
	mSoundPicker->setFixedSize(mSoundPicker->sizeHint());
	layout->addWidget(mSoundPicker);
	layout->addSpacing(2*KDialog::spacingHint());
	layout->addStretch();

	// Colour choice drop-down list
	QHBox* box;
	mBgColourChoose = createBgColourChooser(mReadOnly, &box, mDisplayAlarmsFrame);
	mBgColourChoose->setFixedSize(mBgColourChoose->sizeHint());
	connect(mBgColourChoose, SIGNAL(highlighted(const QColor&)), SLOT(slotBgColourSelected(const QColor&)));
	layout->addWidget(box);

	// Acknowledgement confirmation required - default = no confirmation
	layout = new QHBoxLayout(frameLayout);
	mConfirmAck = createConfirmAckCheckbox(mReadOnly, mDisplayAlarmsFrame);
	mConfirmAck->setFixedSize(mConfirmAck->sizeHint());
	layout->addWidget(mConfirmAck);
	layout->addSpacing(2*KDialog::spacingHint());
	layout->addStretch();

	// Font and colour choice drop-down list
	mFontColourButton = new FontColourButton(mDisplayAlarmsFrame);
	mFontColourButton->setFixedSize(mFontColourButton->sizeHint());
	if (mReadOnly)
		mFontColourButton->hide();
	mFontColourButton->setReadOnly(mReadOnly);
	connect(mFontColourButton, SIGNAL(selected()), SLOT(slotFontColourSelected()));
	layout->addWidget(mFontColourButton);

	// Reminder
	static const QString reminderText = i18n("Enter how long in advance of the main alarm to display a reminder alarm.");
	mReminder = new Reminder(i18n("Rem&inder:"),
	                         i18n("Check to additionally display a reminder in advance of the main alarm time(s)."),
	                         QString("%1\n\n%2").arg(reminderText).arg(TimeSpinBox::shiftWhatsThis()),
	                         true, mDisplayAlarmsFrame);
	mReminder->setFixedSize(mReminder->sizeHint());
	mReminder->setReadOnly(mReadOnly);
	frameLayout->addWidget(mReminder, 0, Qt::AlignLeft);

	// Top-adjust the controls
	mFilePadding = new QHBox(mDisplayAlarmsFrame);
	frameLayout->addWidget(mFilePadding);
	frameLayout->setStretchFactor(mFilePadding, 1);
}

/******************************************************************************
 * Set up the command alarm dialog controls.
 */
void EditAlarmDlg::initCommand(QWidget* parent)
{
	mCommandFrame = new QFrame(parent);
	mCommandFrame->setFrameStyle(QFrame::NoFrame);
	QBoxLayout* layout = new QVBoxLayout(mCommandFrame);

	mCommandMessageEdit = new LineEdit(true, mCommandFrame);
	mCommandMessageEdit->setReadOnly(mReadOnly);
	QWhatsThis::add(mCommandMessageEdit, i18n("Enter a shell command to execute."));
	layout->addWidget(mCommandMessageEdit);
	layout->addStretch();
}

/******************************************************************************
 * Set up the email alarm dialog controls.
 */
void EditAlarmDlg::initEmail(QWidget* parent)
{
	mEmailFrame = new QFrame(parent);
	mEmailFrame->setFrameStyle(QFrame::NoFrame);
	QBoxLayout* layout = new QVBoxLayout(mEmailFrame, 0, spacingHint());
	QGridLayout* grid = new QGridLayout(layout, 2, 3, spacingHint());
	grid->setColStretch(1, 1);

	// Email recipients
	QLabel* label = new QLabel(i18n("Email addressee", "To:"), mEmailFrame);
	label->setFixedSize(label->sizeHint());
	grid->addWidget(label, 0, 0);

	mEmailToEdit = new LineEdit(false, mEmailFrame);
	mEmailToEdit->setMinimumSize(mEmailToEdit->sizeHint());
	mEmailToEdit->setReadOnly(mReadOnly);
	QWhatsThis::add(mEmailToEdit,
	      i18n("Enter the addresses of the email recipients. Separate multiple addresses by "
	           "commas or semicolons."));
	grid->addWidget(mEmailToEdit, 0, 1);

	if (!mReadOnly)
	{
		QPushButton* button = new QPushButton(mEmailFrame);
		button->setPixmap(SmallIcon("contents"));
		button->setFixedSize(button->sizeHint());
		connect(button, SIGNAL(clicked()), SLOT(openAddressBook()));
		QToolTip::add(button, i18n("Open address book"));
		QWhatsThis::add(button, i18n("Select email addresses from your address book."));
		grid->addWidget(button, 0, 2);
	}

	// Email subject
	label = new QLabel(i18n("Email subject", "Sub&ject:"), mEmailFrame);
	label->setFixedSize(label->sizeHint());
	grid->addWidget(label, 1, 0);

	mEmailSubjectEdit = new LineEdit(false, mEmailFrame);
	mEmailSubjectEdit->setMinimumSize(mEmailSubjectEdit->sizeHint());
	mEmailSubjectEdit->setReadOnly(mReadOnly);
	label->setBuddy(mEmailSubjectEdit);
	QWhatsThis::add(mEmailSubjectEdit, i18n("Enter the email subject."));
	grid->addMultiCellWidget(mEmailSubjectEdit, 1, 1, 1, 2);

	// Email body
	mEmailMessageEdit = new TextEdit(mEmailFrame);
	mEmailMessageEdit->setReadOnly(mReadOnly);
	QWhatsThis::add(mEmailMessageEdit, i18n("Enter the email message."));
	layout->addWidget(mEmailMessageEdit);

	// Email attachments
	grid = new QGridLayout(layout, 2, 3, spacingHint());
	label = new QLabel(i18n("Attachment&s:"), mEmailFrame);
	label->setFixedSize(label->sizeHint());
	grid->addWidget(label, 0, 0);

	mEmailAttachList = new QComboBox(true, mEmailFrame);
	mEmailAttachList->setMinimumSize(mEmailAttachList->sizeHint());
	mEmailAttachList->lineEdit()->setReadOnly(true);
QListBox* list = mEmailAttachList->listBox();
QRect rect = list->geometry();
list->setGeometry(rect.left() - 50, rect.top(), rect.width(), rect.height());
	label->setBuddy(mEmailAttachList);
	QWhatsThis::add(mEmailAttachList,
	      i18n("Files to send as attachments to the email."));
	grid->addWidget(mEmailAttachList, 0, 1);
	grid->setColStretch(1, 1);

	if (!mReadOnly)
	{
		QPushButton* button = new QPushButton(i18n("Add..."), mEmailFrame);
		connect(button, SIGNAL(clicked()), SLOT(slotAddAttachment()));
		QWhatsThis::add(button, i18n("Add an attachment to the email."));
		grid->addWidget(button, 0, 2);

		mEmailRemoveButton = new QPushButton(i18n("Remo&ve"), mEmailFrame);
		connect(mEmailRemoveButton, SIGNAL(clicked()), SLOT(slotRemoveAttachment()));
		QWhatsThis::add(mEmailRemoveButton, i18n("Remove the highlighted attachment from the email."));
		grid->addWidget(mEmailRemoveButton, 1, 2);
	}

	// BCC email to sender
	mEmailBcc = new CheckBox(i18n("Copy email to &self"), mEmailFrame);
	mEmailBcc->setFixedSize(mEmailBcc->sizeHint());
	mEmailBcc->setReadOnly(mReadOnly);
	QWhatsThis::add(mEmailBcc,
	      i18n("If checked, the email will be blind copied to you."));
	grid->addMultiCellWidget(mEmailBcc, 1, 1, 0, 1, Qt::AlignLeft);
}

/******************************************************************************
 * Set the dialog's action and the action's text.
 */
void EditAlarmDlg::setAction(KAEvent::Action action, const QString& text)
{
	QRadioButton* radio;
	switch (action)
	{
		case KAEvent::FILE:
			radio = mFileRadio;
			mFileMessageEdit->setText(text);
			break;
		case KAEvent::COMMAND:
			radio = mCommandRadio;
			mCommandMessageEdit->setText(text);
			break;
		case KAEvent::EMAIL:
			radio = mEmailRadio;
			mEmailMessageEdit->setText(text);
			break;
		case KAEvent::MESSAGE:
		default:
			radio = mMessageRadio;
			mTextMessageEdit->setText(text);
			break;
	}
	mActionGroup->setButton(mActionGroup->id(radio));
}

/******************************************************************************
 * Create a widget to choose the alarm message background colour.
 */
ColourCombo* EditAlarmDlg::createBgColourChooser(bool readOnly, QHBox** box, QWidget* parent, const char* name)
{
	*box = new QHBox(parent);   // this is to control the QWhatsThis text display area
	QLabel* label = new QLabel(i18n("&Background color:"), *box);
	label->setFixedSize(label->sizeHint());
	ColourCombo* widget = new ColourCombo(*box, name);
	QSize size = widget->sizeHint();
	widget->setMinimumHeight(size.height() + 4);
	widget->setReadOnly(readOnly);
	QToolTip::add(widget, i18n("Message color"));
	label->setBuddy(widget);
	(*box)->setFixedHeight((*box)->sizeHint().height());
	QWhatsThis::add(*box, i18n("Choose the background color for the alarm message."));
	return widget;
}

/******************************************************************************
 * Create an "acknowledgement confirmation required" checkbox.
 */
CheckBox* EditAlarmDlg::createConfirmAckCheckbox(bool readOnly, QWidget* parent, const char* name)
{
	CheckBox* widget = new CheckBox(i18n("Confirm ac&knowledgement"), parent, name);
	widget->setReadOnly(readOnly);
	QWhatsThis::add(widget,
	      i18n("Check to be prompted for confirmation when you acknowledge the alarm."));
	return widget;
}

/******************************************************************************
 * Create an "cancel if late" checkbox.
 */
CheckBox* EditAlarmDlg::createLateCancelCheckbox(bool readOnly, QWidget* parent, const char* name)
{
	CheckBox* widget = new CheckBox(i18n("Ca&ncel if late"), parent, name);
	widget->setReadOnly(readOnly);
	QWhatsThis::add(widget,
	      i18n("If checked, the alarm will be canceled if it cannot be triggered within 1 "
	           "minute of the specified time. Possible reasons for not triggering include your "
	           "being logged off, X not running, or the alarm daemon not running.\n\n"
	           "If unchecked, the alarm will be triggered at the first opportunity after "
	           "the specified time, regardless of how late it is."));
	return widget;
}

/******************************************************************************
 * Save the state of all controls.
 */
void EditAlarmDlg::saveState(const KAEvent* event)
{
	mSavedEvent          = 0;
	if (event)
		mSavedEvent = new KAEvent(*event);
	mSavedTypeRadio      = mActionGroup->selected();
	mSavedBeep           = mSoundPicker->beep();
	mSavedSoundFile      = mSoundPicker->file();
	mSavedConfirmAck     = mConfirmAck->isChecked();
	mSavedFont           = mFontColourButton->font();
	mSavedFgColour       = mFontColourButton->fgColour();
	mSavedBgColour       = mBgColourChoose->color();
	mSavedReminder       = mReminder->getMinutes();
	checkText(mSavedTextFileCommandMessage, false);
	mSavedEmailTo        = mEmailToEdit->text();
	mSavedEmailSubject   = mEmailSubjectEdit->text();
	mSavedEmailAttach.clear();
	for (int i = 0;  i < mEmailAttachList->count();  ++i)
		mSavedEmailAttach += mEmailAttachList->text(i);
	mSavedEmailBcc       = mEmailBcc->isChecked();
	mSavedDateTime       = mTimeWidget->getDateTime(false, false);
	mSavedLateCancel     = mLateCancel->isChecked();
	mSavedRecurrenceType = mRecurrenceEdit->repeatType();
}

/******************************************************************************
 * Check whether any of the controls other than the deferral time has changed
 * state since the dialog was first displayed.
 */
bool EditAlarmDlg::stateChanged() const
{
	QString textFileCommandMessage;
	checkText(textFileCommandMessage, false);
	if (mSavedTypeRadio        != mActionGroup->selected()
	||  mSavedDateTime         != mTimeWidget->getDateTime(false, false)
	||  mSavedLateCancel       != mLateCancel->isChecked()
	||  textFileCommandMessage != mSavedTextFileCommandMessage
	||  mSavedRecurrenceType   != mRecurrenceEdit->repeatType())
		return true;
	if (mMessageRadio->isOn()  ||  mFileRadio->isOn())
	{
		if (mSavedBeep       != mSoundPicker->beep()
		||  mSavedSoundFile  != mSoundPicker->file()
		||  mSavedConfirmAck != mConfirmAck->isChecked()
		||  mSavedFont       != mFontColourButton->font()
		||  mSavedFgColour   != mFontColourButton->fgColour()
		||  mSavedBgColour   != mBgColourChoose->color()
		||  mSavedReminder   != mReminder->getMinutes())
			return true;
	}
	else if (mEmailRadio->isOn())
	{
		QStringList emailAttach;
		for (int i = 0;  i < mEmailAttachList->count();  ++i)
			emailAttach += mEmailAttachList->text(i);
		if (mSavedEmailTo      != mEmailToEdit->text()
		||  mSavedEmailSubject != mEmailSubjectEdit->text()
		||  mSavedEmailAttach  != emailAttach
		||  mSavedEmailBcc     != mEmailBcc->isChecked())
			return true;
	}
	if (mSavedEvent->recurType() != KAEvent::NO_RECUR)
	{
		if (!mSavedEvent->recurrence())
			return true;
		KAEvent event;
		event.setTime(mSavedEvent->startDateTime().dateTime());
		mRecurrenceEdit->updateEvent(event);
		if (!event.recurrence())
			return true;
		event.recurrence()->setFloats(mSavedEvent->startDateTime().isDateOnly());
		if (*event.recurrence() != *mSavedEvent->recurrence()
		||  event.exceptionDates() != mSavedEvent->exceptionDates()
		||  event.exceptionDateTimes() != mSavedEvent->exceptionDateTimes())
			return true;
	}
	return false;
}

/******************************************************************************
 * Get the currently entered message data.
 * The data is returned in the supplied Event instance.
 */
void EditAlarmDlg::getEvent(KAEvent& event)
{
	if (!mSavedEvent  ||  stateChanged())
	{
		// It's a new event, or the edit controls have changed
		event.set(mAlarmDateTime.dateTime(), mAlarmMessage, mBgColourChoose->color(),
		          mFontColourButton->fgColour(), mFontColourButton->font(), getAlarmType(),
		          getAlarmFlags());
		event.setAudioFile(mSoundPicker->file());
		event.setEmail(mEmailAddresses, mEmailSubjectEdit->text(), mEmailAttachments);
		event.setReminder(mReminder->getMinutes());
		if (mRecurrenceEdit->repeatType() != RecurrenceEdit::NO_RECUR)
		{
			mRecurrenceEdit->updateEvent(event);
			mAlarmDateTime = event.startDateTime();
			if (mDeferDateTime.isValid()  &&  mDeferDateTime < mAlarmDateTime)
			{
				bool deferral = true;
				bool deferReminder = false;
				int reminder = mReminder->getMinutes();
				if (reminder)
				{
					DateTime remindTime = mAlarmDateTime.addMins(-reminder);
					if (mDeferDateTime >= remindTime)
					{
						if (remindTime > QDateTime::currentDateTime())
							deferral = false;    // ignore deferral if it's after next reminder
						else if (mDeferDateTime > remindTime)
							deferReminder = true;    // it's the reminder which is being deferred
					}
				}
				if (deferral)
					event.defer(mDeferDateTime, deferReminder, false);
			}
		}
	}
	else
	{
		// Only the deferral time may have changed
		event = *mSavedEvent;
		if (mSavedEvent->deferred())
		{
			if (mDeferDateTime.isValid())
				event.defer(mDeferDateTime, event.reminderDeferral(), false);
			else
				event.cancelDefer();
		}
	}
}

/******************************************************************************
 * Get the currently specified alarm flag bits.
 */
int EditAlarmDlg::getAlarmFlags() const
{
	bool displayAlarm = mMessageRadio->isOn() || mFileRadio->isOn();
	return (displayAlarm && mSoundPicker->beep()          ? KAEvent::BEEP : 0)
	     | (mLateCancel->isChecked()                      ? KAEvent::LATE_CANCEL : 0)
	     | (displayAlarm && mConfirmAck->isChecked()      ? KAEvent::CONFIRM_ACK : 0)
	     | (mEmailRadio->isOn() && mEmailBcc->isChecked() ? KAEvent::EMAIL_BCC : 0)
	     | (mRecurrenceEdit->repeatType() == RecurrenceEdit::AT_LOGIN ? KAEvent::REPEAT_AT_LOGIN : 0)
	     | (mAlarmDateTime.isDateOnly()                   ? KAEvent::ANY_TIME : 0)
	     | (mFontColourButton->defaultFont()              ? KAEvent::DEFAULT_FONT : 0);
}

/******************************************************************************
 * Get the currently selected alarm type.
 */
KAEvent::Action EditAlarmDlg::getAlarmType() const
{
	return mFileRadio->isOn()    ? KAEvent::FILE
	     : mCommandRadio->isOn() ? KAEvent::COMMAND
	     : mEmailRadio->isOn()   ? KAEvent::EMAIL
	     :                         KAEvent::MESSAGE;
}

/******************************************************************************
*  Called when the dialog's size has changed.
*  Records the new size (adjusted to ignore the optional height of the deferred
*  time edit widget) in the config file.
*/
void EditAlarmDlg::resizeEvent(QResizeEvent* re)
{
	if (isVisible())
	{
		mBasicSize = re->size();
		mBasicSize.setHeight(mBasicSize.height() - mDeferGroupHeight);
		theApp()->writeConfigWindowSize("EditDialog", mBasicSize);
	}
	KDialog::resizeEvent(re);
}

/******************************************************************************
*  Called when the OK button is clicked.
*  Set up the new alarm.
*/
void EditAlarmDlg::slotOk()
{
	if (activePageIndex() == mRecurPageIndex  &&  mRecurrenceEdit->repeatType() == RecurrenceEdit::AT_LOGIN)
		mTimeWidget->setDateTime(mRecurrenceEdit->endDateTime());
	QWidget* errWidget;
	bool timedRecurrence = mRecurrenceEdit->isTimedRepeatType();
#if 0
	if (mTemplate)
	{
		// Check that the template name is not blank and is unique
		QString errmsg;
		QString name = mTemplateName->text();
		if (name.isEmpty())
			errmsg = i18n("You must enter a name for the alarm template");
		else if (name != mSavedTemplateName)
		{
			AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen();
			if (cal  &&  KAEvent::findTemplateName(*cal, name).valid())
			errmsg = i18n("Template name is already in use");
		}
		if (!errmsg.isEmpty())
		{
			mTemplateName->setFocus();
			KMessageBox::sorry(this, errmsg);
			return;
		}
	}
	else
#endif
	mAlarmDateTime = mTimeWidget->getDateTime(!timedRecurrence, false, &errWidget);
	if (errWidget)
	{
		showPage(mMainPageIndex);
		errWidget->setFocus();
		mTimeWidget->getDateTime();   // display the error message now
	}
	else if (checkEmailData())
	{
		if (timedRecurrence)
		{
			QDateTime now = QDateTime::currentDateTime();
			if (mAlarmDateTime.date() < now.date()
			||  !mAlarmDateTime.isDateOnly() && mAlarmDateTime.time() < now.time())
			{
				// A timed recurrence has an entered start date which
				// has already expired, so we must adjust it.
				KAEvent event;
				getEvent(event);
				if (event.nextOccurrence(now, mAlarmDateTime) == KAEvent::NO_OCCURRENCE)
				{
					KMessageBox::sorry(this, i18n("Recurrence has already expired"));
					return;
				}
			}
		}
		QString errmsg;
{'fraction_non_alphanumeric': 0.16489193629929222, 'fraction_numerical': 0.007472826086956522, 'mean_word_length': 5.30762331838565, 'pattern_counts': {'":': 0, '<': 79, '<?xml version=': 0, '>': 697, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '305922', 'n_tokens_mistral': 21598, 'n_tokens_neox': 20167, 'n_words': 4333}