1 //=============================================================================
2 //
3 //   File : libkvidialog.cpp
4 //   Creation date : Sat Sep 15 2001 01:13:25 by Szymon Stefanek
5 //
6 //   This file is part of the KVIrc IRC client distribution
7 //   Copyright (C) 2001-2010 Szymon Stefanek (pragma at kvirc dot net)
8 //
9 //   This program is FREE software. You can redistribute it and/or
10 //   modify it under the terms of the GNU General Public License
11 //   as published by the Free Software Foundation; either version 2
12 //   of the License, or (at your option) any later version.
13 //
14 //   This program is distributed in the HOPE that it will be USEFUL,
15 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
16 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 //   See the GNU General Public License for more details.
18 //
19 //   You should have received a copy of the GNU General Public License
20 //   along with this program. If not, write to the Free Software Foundation,
21 //   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 //
23 //=============================================================================
24 
25 #include "libkvidialog.h"
26 
27 #include "KviLocale.h"
28 #include "KviModule.h"
29 #include "KviModuleManager.h"
30 #include "KviError.h"
31 #include "KviApplication.h"
32 #include "KviIconManager.h"
33 #include "KviConsoleWindow.h"
34 #include "KviIconManager.h"
35 #include "KviKvsScript.h"
36 #include "KviMessageBox.h"
37 #include "KviTalHBox.h"
38 
39 #include <QMessageBox>
40 #include <QLayout>
41 #include <QLineEdit>
42 #include <QLabel>
43 #include <QPushButton>
44 #include <QDesktopWidget>
45 #include <QEvent>
46 #include <QCloseEvent>
47 
48 #include <QTextEdit>
49 
50 static KviPointerList<QWidget> * g_pDialogModuleDialogList;
51 
KviKvsCallbackMessageBox(const QString & szCaption,const QString & szText,const QString & szIcon,const QString & szButton0,const QString & szButton1,const QString & szButton2,const QString & szCode,KviKvsVariantList * pMagicParams,KviWindow * pWindow,bool modal)52 KviKvsCallbackMessageBox::KviKvsCallbackMessageBox(
53     const QString & szCaption,
54     const QString & szText,
55     const QString & szIcon,
56     const QString & szButton0,
57     const QString & szButton1,
58     const QString & szButton2,
59     const QString & szCode,
60     KviKvsVariantList * pMagicParams,
61     KviWindow * pWindow, bool modal)
62     : QMessageBox(nullptr),
63       KviKvsCallbackObject("dialog.message", pWindow, szCode, pMagicParams, 0)
64 {
65 	setObjectName("dialog_message");
66 	setWindowTitle(szCaption);
67 	setText(szText);
68 	setIcon(QMessageBox::NoIcon);
69 	setModal(modal);
70 	QMessageBox::StandardButtons buttons;
71 	bool btn = false;
72 	if(!szButton0.isEmpty())
73 	{
74 		btn = true;
75 		buttons = QMessageBox::Yes;
76 	}
77 	if(!szButton1.isEmpty())
78 	{
79 		btn = true;
80 		buttons |= QMessageBox::No;
81 	}
82 	if(!szButton2.isEmpty())
83 	{
84 		btn = true;
85 		buttons |= QMessageBox::Cancel;
86 	}
87 
88 	if(!btn)
89 		buttons = QMessageBox::Ok;
90 	setStandardButtons(buttons);
91 	setDefaultButton(QMessageBox::Yes);
92 	if(szButton2.isEmpty())
93 		setEscapeButton(QMessageBox::No);
94 	else
95 		setEscapeButton(QMessageBox::Cancel);
96 
97 	g_pDialogModuleDialogList->append(this);
98 
99 	QPixmap * pix = g_pIconManager->getImage(szIcon);
100 
101 	if(pix)
102 		setIconPixmap(*pix);
103 	else
104 	{
105 		if(KviQString::equalCI(szIcon, "information"))
106 			setIcon(QMessageBox::Information);
107 		else if(KviQString::equalCI(szIcon, "warning"))
108 			setIcon(QMessageBox::Warning);
109 		else if(KviQString::equalCI(szIcon, "critical"))
110 			setIcon(QMessageBox::Critical);
111 	}
112 	if(!szButton0.isEmpty())
113 		setButtonText(QMessageBox::Yes, szButton0);
114 	if(!szButton1.isEmpty())
115 		setButtonText(QMessageBox::No, szButton1);
116 	if(!szButton2.isEmpty())
117 		setButtonText(QMessageBox::Cancel, szButton2);
118 }
119 
~KviKvsCallbackMessageBox()120 KviKvsCallbackMessageBox::~KviKvsCallbackMessageBox()
121 {
122 	g_pDialogModuleDialogList->removeRef(this);
123 }
124 
done(int code)125 void KviKvsCallbackMessageBox::done(int code)
126 {
127 	QMessageBox::done(code);
128 
129 	kvs_int_t iVal = 0;
130 
131 	switch(code)
132 	{
133 		case QMessageBox::No:
134 			iVal = 1;
135 			break;
136 		case QMessageBox::Cancel:
137 			iVal = 2;
138 			break;
139 		case 0:
140 			// user closed the dialog, fake an "escape button" press
141 			if(standardButtons() & QMessageBox::Cancel)
142 				iVal = 2;
143 			else
144 				iVal = 1;
145 			break;
146 	}
147 
148 	KviKvsVariantList params;
149 	params.append(new KviKvsVariant(iVal));
150 
151 	execute(&params);
152 	deleteLater();
153 }
154 
155 /*
156 	@doc: dialog.message
157 	@type:
158 		command
159 	@title:
160 		dialog.message
161 	@short:
162 		Shows a message box
163 	@syntax:
164 		dialog.message [-b] (<caption>,<message_text>,<icon>,<button0>[,<button1>[,<button2>[,<magic1>[,<magic2>[...]]]]])
165 		{
166 			<callback_command>
167 		}
168 	@description:
169 		Shows a message dialog box with the specified <caption>, <message_text>, <icon> and
170 		buttons.[br]
171 		<caption> is a text string that will appear in the caption of the dialog box.[br]
172 		<message_text> is a text string that will appear in the dialog box and can contain HTML formatting.[br]
173 		<icon> is an [doc:image_id]image identifier[/doc] that defines an icon to be placed in the dialog box.
174 		<icon> can be a relative or absolute path to an image file, a signed number (in that case it defines
175 		an internal KVIrc image) or one of the special strings [i]critical[/i], [i]information[/i] and [i]warning[/i].[br]
176 		<button0> is the text of the first button (on the left).[br]
177 		<button1> is the text of the second button (if empty or specified, only one button will appear in the dialog).[br]
178 		<button2> is the text of the third button (if empty or specified, only two buttons will appear in the dialog).[br]
179 		The first button is the default button - it is activated when the user presses the
180 		enter key. The third (or the second if only two buttons are present) is treated as the escape button
181 		and is activated when the user presses the Esc key or closes the dialog with the window manager close button.[br]
182 		If one of the button text strings starts with a [i]default=[/i] prefix then that button is assumed
183 		to be the default button of the dialog.[br]
184 		If one of the button text strings starts with a [i]escape=[/i] prefix then that button is assumed
185 		to be the escape button of the dialog.[br]
186 		<magic1>, <magic2>... are the magic parameters - evaluated at dialog.message call time and passed
187 		to the <callback_command> as positional parameters.[br]
188 		If the -b or -modal switch is specified the dialog will have blocking modal behaviour -
189 		it will appear above its parent widget and block its input until the dialog is closed.[br]
190 		Once the dialog is displayed, the user will click one of the buttons. At this point the dialog
191 		is hidden and the <callback_command> is executed, passing the number of the button clicked
192 		as $0 and the magic parameters as positional parameters $1, $2, $3.[br]
193 	@examples:
194 		[example]
195 		[comment]# Just a warning dialog[/comment]
196 		dialog.message("Warning","You're being <b>warned</b>",warning,"OK"){ echo The user clicked OK; }
197 		[comment]# A question[/comment]
198 		dialog.message("And now?","What do you want to do?",information,"Go home","Watch TV","Scream")
199 		{
200 			if($0 == 0)echo "The user wants to go home"
201 			else if($0 == 1)echo "The user wants to watch TV"
202 			else echo "The user wants to scream!"
203 		}
204 		[/example]
205 */
206 
dialog_kvs_cmd_message(KviKvsModuleCallbackCommandCall * c)207 static bool dialog_kvs_cmd_message(KviKvsModuleCallbackCommandCall * c)
208 {
209 	QString szCaption, szMessage, szIcon, szButton0, szButton1, szButton2;
210 	KviKvsVariantList params;
211 
212 	KVSM_PARAMETERS_BEGIN(c)
213 	KVSM_PARAMETER("caption", KVS_PT_STRING, 0, szCaption)
214 	KVSM_PARAMETER("message", KVS_PT_STRING, 0, szMessage)
215 	KVSM_PARAMETER("icon", KVS_PT_STRING, 0, szIcon)
216 	KVSM_PARAMETER("button0", KVS_PT_STRING, KVS_PF_OPTIONAL, szButton0)
217 	KVSM_PARAMETER("button1", KVS_PT_STRING, KVS_PF_OPTIONAL, szButton1)
218 	KVSM_PARAMETER("button2", KVS_PT_STRING, KVS_PF_OPTIONAL, szButton2)
219 	KVSM_PARAMETER("magic", KVS_PT_VARIANTLIST, KVS_PF_OPTIONAL, params)
220 	KVSM_PARAMETERS_END(c)
221 	bool modal;
222 	if(c->hasSwitch('b', "modal"))
223 		modal = true;
224 	else
225 		modal = false;
226 	QString szCmd = c->callback()->code();
227 
228 	KviKvsCallbackMessageBox * box = new KviKvsCallbackMessageBox(
229 	    szCaption, szMessage, szIcon, szButton0, szButton1, szButton2, szCmd, &params, c->window(), modal);
230 	box->show();
231 
232 	return true;
233 }
234 
KviKvsCallbackTextInput(const QString & szCaption,const QString & szLabel,const QString & szDefaultText,const QString & szIcon,bool bMultiLine,bool bPassword,const QString & szButton0,const QString & szButton1,const QString & szButton2,const QString & szCode,KviKvsVariantList * pMagicParams,KviWindow * pWindow,bool modal)235 KviKvsCallbackTextInput::KviKvsCallbackTextInput(
236     const QString & szCaption,
237     const QString & szLabel,
238     const QString & szDefaultText,
239     const QString & szIcon,
240     bool bMultiLine,
241     bool bPassword,
242     const QString & szButton0,
243     const QString & szButton1,
244     const QString & szButton2,
245     const QString & szCode,
246     KviKvsVariantList * pMagicParams,
247     KviWindow * pWindow, bool modal)
248     : QDialog(), KviKvsCallbackObject("dialog.textinput", pWindow, szCode, pMagicParams, 0)
249 {
250 	setObjectName("dialog_textinput");
251 	g_pDialogModuleDialogList->append(this);
252 	setWindowIcon(*(g_pIconManager->getSmallIcon(KviIconManager::KVIrc)));
253 	setModal(modal);
254 	setWindowTitle(szCaption);
255 
256 	QGridLayout * g = new QGridLayout(this);
257 
258 	QPixmap * pix = g_pIconManager->getImage(szIcon);
259 
260 	if(pix)
261 	{
262 		QLabel * il = new QLabel(this);
263 		il->setPixmap(*pix);
264 		il->setAlignment(Qt::AlignCenter);
265 		g->addWidget(il, 0, 0);
266 		QLabel * tl = new QLabel(szLabel, this);
267 		g->addWidget(tl, 0, 1);
268 	}
269 	else
270 	{
271 		QLabel * tl = new QLabel(szLabel, this);
272 		g->addWidget(tl, 0, 0, 1, 2);
273 	}
274 
275 	g->setColumnStretch(1, 1);
276 
277 	m_bMultiLine = bMultiLine;
278 	m_bPassword = bPassword;
279 
280 	if(m_bMultiLine)
281 	{
282 		m_pEdit = new QTextEdit(this);
283 		((QTextEdit *)m_pEdit)->setPlainText(szDefaultText);
284 		((QTextEdit *)m_pEdit)->selectAll();
285 	}
286 	else
287 	{
288 		m_pEdit = new QLineEdit(this);
289 		if(m_bPassword)
290 			((QLineEdit *)m_pEdit)->setEchoMode(QLineEdit::Password);
291 		((QLineEdit *)m_pEdit)->setText(szDefaultText);
292 		((QLineEdit *)m_pEdit)->selectAll();
293 	}
294 
295 	g->addWidget(m_pEdit, 1, 1, 1, 1);
296 
297 	KviTalHBox * box = new KviTalHBox(this);
298 	g->addWidget(box, 2, 1, 1, 2);
299 
300 	m_iEscapeButton = -1;
301 	m_iDefaultButton = 0;
302 
303 	if(!szButton0.isEmpty())
304 	{
305 		QString szB = szButton0;
306 		bool bDef = false;
307 		if(KviQString::equalCIN(szB, "default=", 8))
308 		{
309 			bDef = true;
310 			szB.remove(0, 8);
311 			m_iDefaultButton = 0;
312 		}
313 		else if(KviQString::equalCIN(szB, "escape=", 7))
314 		{
315 			szB.remove(0, 7);
316 			m_iEscapeButton = 0;
317 		}
318 		QPushButton * pb1 = new QPushButton(szB, box);
319 		if(bDef)
320 			pb1->setDefault(true);
321 		connect(pb1, SIGNAL(clicked()), this, SLOT(b0Clicked()));
322 	}
323 
324 	if(!szButton1.isEmpty())
325 	{
326 		QString szB = szButton1;
327 		bool bDef = false;
328 		if(KviQString::equalCIN(szB, "default=", 8))
329 		{
330 			bDef = true;
331 			szB.remove(0, 8);
332 			m_iDefaultButton = 1;
333 		}
334 		else if(KviQString::equalCIN(szB, "escape=", 7))
335 		{
336 			szB.remove(0, 7);
337 			m_iEscapeButton = 1;
338 		}
339 		QPushButton * pb2 = new QPushButton(szB, box);
340 		if(bDef)
341 			pb2->setDefault(true);
342 		connect(pb2, SIGNAL(clicked()), this, SLOT(b1Clicked()));
343 	}
344 
345 	if(!szButton2.isEmpty())
346 	{
347 		QString szB = szButton2;
348 		bool bDef = false;
349 		if(KviQString::equalCIN(szB, "default=", 8))
350 		{
351 			bDef = true;
352 			szB.remove(0, 8);
353 			m_iDefaultButton = 2;
354 		}
355 		else if(KviQString::equalCIN(szB, "escape=", 7))
356 		{
357 			szB.remove(0, 7);
358 			m_iEscapeButton = 2;
359 		}
360 		QPushButton * pb3 = new QPushButton(szB, box);
361 		if(bDef)
362 			pb3->setDefault(true);
363 		connect(pb3, SIGNAL(clicked()), this, SLOT(b2Clicked()));
364 	}
365 
366 	if(m_iEscapeButton < 0)
367 	{
368 		//no escape button explicitly set, search for one:
369 		if(!szButton2.isEmpty())
370 			m_iEscapeButton = 2;
371 		else if(!szButton1.isEmpty())
372 			m_iEscapeButton = 1;
373 		else
374 			m_iEscapeButton = 0;
375 	}
376 }
377 
~KviKvsCallbackTextInput()378 KviKvsCallbackTextInput::~KviKvsCallbackTextInput()
379 {
380 	g_pDialogModuleDialogList->removeRef(this);
381 }
382 
b0Clicked()383 void KviKvsCallbackTextInput::b0Clicked()
384 {
385 	done(0 + 10);
386 }
387 
b1Clicked()388 void KviKvsCallbackTextInput::b1Clicked()
389 {
390 	done(1 + 10);
391 }
392 
b2Clicked()393 void KviKvsCallbackTextInput::b2Clicked()
394 {
395 	done(2 + 10);
396 }
397 
closeEvent(QCloseEvent * e)398 void KviKvsCallbackTextInput::closeEvent(QCloseEvent * e)
399 {
400 	e->ignore();
401 	done(m_iEscapeButton + 10);
402 }
403 
done(int code)404 void KviKvsCallbackTextInput::done(int code)
405 {
406 	if(code >= 10)
407 	{
408 		code -= 10;
409 	}
410 	else
411 	{
412 		switch(code)
413 		{
414 			case QDialog::Accepted:
415 				code = m_iDefaultButton;
416 				break;
417 			default:
418 				code = m_iEscapeButton;
419 				break;
420 		}
421 	}
422 
423 	QString txt;
424 
425 	if(m_bMultiLine)
426 	{
427 		txt = ((QTextEdit *)m_pEdit)->toPlainText();
428 	}
429 	else
430 	{
431 		txt = ((QLineEdit *)m_pEdit)->text();
432 	}
433 
434 	KviKvsVariantList params;
435 	params.append(new KviKvsVariant((kvs_int_t)code));
436 	params.append(new KviKvsVariant(txt));
437 
438 	execute(&params);
439 
440 	//QDialog::done(code);
441 
442 	deleteLater();
443 }
444 
showEvent(QShowEvent * e)445 void KviKvsCallbackTextInput::showEvent(QShowEvent * e)
446 {
447 	QRect rect = g_pApp->desktop()->screenGeometry(g_pApp->desktop()->primaryScreen());
448 	move((rect.width() - width()) / 2, (rect.height() - height()) / 2);
449 
450 	QDialog::showEvent(e);
451 }
452 
453 /*
454 	@doc: dialog.textinput
455 	@type:
456 		command
457 	@title:
458 		dialog.textinput
459 	@short:
460 		Shows a dialog that accepts user input as text
461 	@syntax:
462 		dialog.textinput [-d=<default text>] [-i=<icon>] [-m] [-b] [-p] (<caption>,<info_text>,<button0>[,<button1>[,<button2>[,<magic1>[,<magic2>[...]]]]])
463 		{
464 			<callback_command>
465 		}
466 	@switches:
467 		!sw: -d=<default_text> | --default=<default_text>
468 		Set the initial text input value to <default_text>
469 		!sw: -i=<icon> | --icon=<icon>
470 		Display the specified icon, to the left of the informational text
471 		!sw: -m | --multiline
472 		Input multi-line text instead of single line
473 		!sw: -p | --password
474 		Display asterisks instead of the characters actually entered
475 	@description:
476 		Shows a text input dialog box with the specified <caption>, <info_text>, <icon> and
477 		buttons.[br]
478 		<caption> is a text string that will appear in the caption of the dialog box.[br]
479 		<info_text> is a fixed text string that will appear in the dialog box and can contain HTML formatting.[br]
480 		<button0> is the text of the first button (on the left).[br]
481 		<button1> is the text of the second button (if empty or not given at all, only one button will appear in the dialog).[br]
482 		<button2> is the text of the third button (if empty or not given, only two buttons will appear in the dialog).[br]
483 		The first button is the default button - it is activated when the user presses the
484 		enter key. The third (or the second if only two buttons are present) is treated as the escape button
485 		and is activated when the user presses the Esc key or closes the dialog with the window manager close button.[br]
486 		If one of the button text strings starts with a [i]default=[/i] prefix then that button is assumed
487 		to be the default button of the dialog.[br]
488 		If one of the button text strings starts with a [i]escape=[/i] prefix then that button is assumed
489 		to be the escape button of the dialog.[br]
490 		If the -m switch is used, the dialog will be a multi-line text input, otherwise the user will be able to
491 		input only a single line of text.[br]
492 		If the -p switch is used, the text will be show as asterisks, useful for sensitive data (passwords).[br]
493 		If the -d switch is used, the initial text input value is set to <default text>.[br]
494 		If the -i switch is used, the dialog displays also the icon <icon> to the left of <info_text>.
495 		<icon> is an image identifier (a relative or absolute path to an image file, or a signed number that maps to an internal KVIrc image). [br]
496 		If the -b or -modal switch is specified the dialog will have blocking modal behaviour:
497 		it will appear above its parent widget and block its input until it's closed.[br]
498 		In that case <icon> is an [doc:image_id]image identifier[/doc] (can be a relative or absolute
499 		path to an image file or a signed number (in that case it defines an internal KVIrc image).[br]
500 		<magic1>,<magic2>... are the magic parameters: evaluated at dialog.textinput call time and passed
501 		to the <callback_command> as positional parameters.[br]
502 		Once the dialog has been shown, the user will click one of the buttons. At this point the dialog
503 		is hidden and the <callback_command> is executed passing the text input value in $1, the number of the button clicked
504 		as $0, and the magic parameters as positional parameters $2, $3, $4....[br]
505 	@examples:
506 		[example]
507 		[comment]# We need a single line reason[/comment]
508 		dialog.textinput -d="Working!" (Away,Please enter the away message,"OK","Cancel")
509 		{
510 			switch($0)
511 			{
512 				case(0):
513 					away $1-
514 				break;
515 				default:
516 					# Cancelled
517 				break;
518 			}
519 		}
520 		[/example]
521 */
522 
dialog_kvs_cmd_textinput(KviKvsModuleCallbackCommandCall * c)523 static bool dialog_kvs_cmd_textinput(KviKvsModuleCallbackCommandCall * c)
524 {
525 	QString szCaption, szInfoText, szIcon, szDefaultText, szButton0, szButton1, szButton2;
526 	KviKvsVariantList params;
527 
528 	KVSM_PARAMETERS_BEGIN(c)
529 	KVSM_PARAMETER("caption", KVS_PT_STRING, 0, szCaption)
530 	KVSM_PARAMETER("info_text", KVS_PT_STRING, 0, szInfoText)
531 	KVSM_PARAMETER("button0", KVS_PT_STRING, KVS_PF_OPTIONAL, szButton0)
532 	KVSM_PARAMETER("button1", KVS_PT_STRING, KVS_PF_OPTIONAL, szButton1)
533 	KVSM_PARAMETER("button2", KVS_PT_STRING, KVS_PF_OPTIONAL, szButton2)
534 	KVSM_PARAMETER("magic", KVS_PT_VARIANTLIST, KVS_PF_OPTIONAL, params)
535 	KVSM_PARAMETERS_END(c)
536 
537 	QString szCmd = c->callback()->code();
538 
539 	c->switches()->getAsStringIfExisting('i', "icon", szIcon);
540 	c->switches()->getAsStringIfExisting('d', "default", szDefaultText);
541 	bool modal;
542 	if(c->hasSwitch('b', "modal"))
543 		modal = true;
544 	else
545 		modal = false;
546 	KviKvsCallbackTextInput * box = new KviKvsCallbackTextInput(
547 	    szCaption, szInfoText, szDefaultText, szIcon, c->switches()->find('m', "multiline"), c->switches()->find('p', "password"),
548 	    szButton0, szButton1, szButton2, szCmd, &params, c->window(), modal);
549 	box->show();
550 
551 	return true;
552 }
553 
KviKvsCallbackFileDialog(const QString & szCaption,const QString & szInitialSelection,const QString & szFilter,const QString & szCode,KviKvsVariantList * pMagicParams,KviWindow * pWindow,bool modal)554 KviKvsCallbackFileDialog::KviKvsCallbackFileDialog(
555     const QString & szCaption,
556     const QString & szInitialSelection,
557     const QString & szFilter,
558     const QString & szCode,
559     KviKvsVariantList * pMagicParams,
560     KviWindow * pWindow, bool modal)
561     : KviFileDialog(
562           szInitialSelection,
563           szFilter,
564           nullptr, // parent
565           nullptr,
566           modal),
567       KviKvsCallbackObject("dialog.file", pWindow, szCode, pMagicParams, 0)
568 {
569 	g_pDialogModuleDialogList->append(this);
570 	setWindowTitle(szCaption);
571 	setObjectName("dialog_file");
572 }
573 
~KviKvsCallbackFileDialog()574 KviKvsCallbackFileDialog::~KviKvsCallbackFileDialog()
575 {
576 	g_pDialogModuleDialogList->removeRef(this);
577 }
578 
done(int code)579 void KviKvsCallbackFileDialog::done(int code)
580 {
581 	KviFileDialog::done(code);
582 	KviKvsVariantList params;
583 
584 	if(code == QDialog::Accepted)
585 	{
586 #ifdef COMPILE_KDE4_SUPPORT
587 		if(mode() == KFile::ExistingOnly)
588 #else
589 		if(fileMode() == QFileDialog::ExistingFiles)
590 #endif
591 		{
592 			KviKvsArray * a = new KviKvsArray();
593 			QStringList sl = selectedFiles();
594 			int idx = 0;
595 			for(auto & it : sl)
596 			{
597 				a->set(idx, new KviKvsVariant(it));
598 				idx++;
599 			}
600 			params.append(new KviKvsVariant(a));
601 		}
602 		else
603 		{
604 			params.append(new KviKvsVariant(selectedFiles().at(0)));
605 		}
606 	}
607 	else
608 	{
609 		params.append(new KviKvsVariant(QString("")));
610 	}
611 
612 	hide(); // ensure we're hidden
613 
614 	execute(&params);
615 	deleteLater();
616 }
617 
618 /*
619 	@doc: dialog.file
620 	@type:
621 		command
622 	@title:
623 		dialog.file
624 	@short:
625 		Shows a file dialog
626 	@syntax:
627 		dialog.file [-b] (<mode>,<caption>[,<initial_selection[,<file_filter>[,<magic1>[,<magic2>[...]]]]]])
628 		{
629 			<callback_command>
630 		}
631 	@description:
632 		Shows an open file dialog box with the specified <caption>, <initial_selection>, and <file_filter>.[br]
633 		<mode> can be [i]open[/i], [i]openm[/i], [i]save[/i] or [i]dir[/i]:[br]
634 		[b]open[/b] causes the dialog to return an existing file[br]
635 		[b]openm[/b] is similar to open but allows returning multiple files as a comma separated list[br]
636 		[b]save[/b] causes the dialog to return any file name (no overwrite confirmation is built in the dialog!)[br]
637 		[b]dir[/b] causes the dialog to return an existing directory name[br]
638 		<mode> defaults to [i]open[/i].[br]
639 		<caption> is a text string that will appear in the caption of the dialog box.[br]
640 		<initial_selection> can be a directory or filename that will be initially selected in the dialog.[br]
641 		Only files matching <file_filter> are selectable. If filter is an empty string, all files are selectable.[br]
642 		In the filter string multiple filters can be specified separated by either two semicolons next to each
643 		other or separated by newlines. To add two filters, one to show all C++ files and one to show all
644 		header files, the filter string could look like [i]C++ Files (*.cpp *.cc *.C *.cxx *.c++);;Header Files (*.h *.hxx *.h++)[/i]
645 		<magic1>, <magic2>... are the magic parameters: evaluated at dialog.message call time and passed
646 		to the <callback_command> as positional parameters.[br]
647 		If the -b or -modal switch is specified the dialog will have non-blocking modal behaviour:
648 		it will appear above its parent widget and block its input until it's closed.[br]
649 		Once the dialog has been shown, the user will select an EXISTING file and click either
650 		Ok or Cancel. At this point the dialog is hidden and the <callback_command> is executed passing the selected file(s) as $0
651 		and the magic parameters as positional parameters $1, $2, $3....[br]
652 		If the user clicks [i]Cancel[/i] or does not select any file the positional parameter $0 will be empty.[br]
653 	@examples:
654 		[example]
655 			dialog.file(open,Choose an audio file,/home/pragma/TheAudio.au,"Audio files (*.au *.wav *.snd)")
656 			{
657 				if("$0" != "")run play $0
658 			}
659 		[/example]
660 */
661 
662 //#warning "Examples for these dialogs!"
663 
dialog_kvs_cmd_file(KviKvsModuleCallbackCommandCall * c)664 static bool dialog_kvs_cmd_file(KviKvsModuleCallbackCommandCall * c)
665 {
666 	QString szMode, szCaption, szInitialSelection, szFilter;
667 	KviKvsVariantList params;
668 
669 	KVSM_PARAMETERS_BEGIN(c)
670 	KVSM_PARAMETER("mode", KVS_PT_STRING, 0, szMode)
671 	KVSM_PARAMETER("caption", KVS_PT_STRING, 0, szCaption)
672 	KVSM_PARAMETER("initial_selection", KVS_PT_STRING, KVS_PF_OPTIONAL, szInitialSelection)
673 	KVSM_PARAMETER("filter", KVS_PT_STRING, KVS_PF_OPTIONAL, szFilter)
674 	KVSM_PARAMETER("magic", KVS_PT_VARIANTLIST, KVS_PF_OPTIONAL, params)
675 	KVSM_PARAMETERS_END(c)
676 
677 	bool modal = c->hasSwitch('b', "modal");
678 
679 	QString szCmd = c->callback()->code();
680 
681 	KviKvsCallbackFileDialog * box = new KviKvsCallbackFileDialog(szCaption, szInitialSelection, szFilter, szCmd, &params, c->window(), modal);
682 
683 	KviFileDialog::FileMode md = KviFileDialog::ExistingFile;
684 
685 	if(KviQString::equalCI(szMode, "open"))
686 		md = KviFileDialog::ExistingFiles;
687 	else if(KviQString::equalCI(szMode, "save"))
688 		md = KviFileDialog::AnyFile;
689 	else if(KviQString::equalCI(szMode, "dir"))
690 		md = KviFileDialog::DirectoryOnly;
691 
692 	box->setFileMode(md);
693 
694 	box->show();
695 
696 	return true;
697 }
698 
KviKvsCallbackImageDialog(const QString & szCaption,const QString & szInitialSelection,int iType,int iMaxSize,const QString & szCode,KviKvsVariantList * pMagicParams,KviWindow * pWindow,bool modal)699 KviKvsCallbackImageDialog::KviKvsCallbackImageDialog(
700     const QString & szCaption,
701     const QString & szInitialSelection,
702     int iType,
703     int iMaxSize,
704     const QString & szCode,
705     KviKvsVariantList * pMagicParams,
706     KviWindow * pWindow, bool modal)
707     : KviImageDialog(nullptr, szCaption, iType, 0, szInitialSelection, iMaxSize, modal), KviKvsCallbackObject("dialog.image", pWindow, szCode, pMagicParams, 0)
708 {
709 	g_pDialogModuleDialogList->append(this);
710 	setObjectName("dialog_image");
711 }
712 
~KviKvsCallbackImageDialog()713 KviKvsCallbackImageDialog::~KviKvsCallbackImageDialog()
714 {
715 	g_pDialogModuleDialogList->removeRef(this);
716 }
717 
done(int code)718 void KviKvsCallbackImageDialog::done(int code)
719 {
720 	KviImageDialog::done(code);
721 	KviKvsVariantList params;
722 
723 	if(code == QDialog::Accepted)
724 	{
725 		params.append(new KviKvsVariant(selectedImage()));
726 	}
727 	else
728 	{
729 		params.append(new KviKvsVariant(QString("")));
730 	}
731 
732 	hide(); // ensure we're hidden
733 
734 	execute(&params);
735 	deleteLater();
736 }
737 
738 /*
739 	@doc: dialog.image
740 	@type:
741 		command
742 	@title:
743 		dialog.image
744 	@short:
745 		Shows a image dialog
746 	@syntax:
747 		dialog.image [-b] (<type>,<caption>,<initial_directory>,[<maxsize>,[,<magic1>[,<magic2>[...]]]]]])
748 		{
749 			<callback_command>
750 		}
751 	@description:
752 		Shows a dialog that allows selecting an [doc:image_id]image_id[doc].
753 		The <type> parameter must be a combination of the following flags:<br>
754 		[b]s[/b] : allow selecting from the KVIrc builtin small icons<br>
755 		[b]f[/b] : allow browsing the local directories<br>
756 		[b]a[/b] : all of the above<br>
757 		The default for <type> is 'a'.<br>
758 		<caption> is the caption string for the dialog.<br>
759 		<initial_directory> makes sense only if 'f' is specified (if <initial_directory> is empty
760 		then the last path used by the image dialog will be used).<br>
761 		<maxsize> is the maximum size of the images for that the preview will be generated:
762 		this is 256000 bytes by default (if unspecified). Don't make it a lot bigger: it can take a lot to make
763 		the thumbnails for bigger images (and it can eat a considerable amount of memory).<br>
764 		<magic1>, <magic2>... are the magic parameters: evaluated at dialog.image call time and passed
765 		to the <callback_command> as positional parameters.[br]
766 		If the -b or -modal switch is specified the dialog will have non-blocking modal behaviour:
767 		it will appear above its parent widget and block its input until it's closed.[br]
768 		Once the dialog has been shown, the user will select an [b]existing[/b] file and click either
769 		OK or Cancel. At this point the dialog is hidden and the <callback_command> is executed passing the selected file(s) as $0
770 		and the magic parameters as positional parameters $1, $2, $3....[br]
771 		If the user clicks [i]Cancel[/i] or does not select any image the positional parameter $0 will be empty.[br]
772 	@examples:
773 		[example]
774 			dialog.image(f,Choose an image file,/home/pragma/,"256000")
775 			{
776 				if("$0" != "")run okular $0
777 			}
778 		[/example]
779 */
780 
781 //#warning "Examples for these dialogs!"
782 
dialog_kvs_cmd_image(KviKvsModuleCallbackCommandCall * c)783 static bool dialog_kvs_cmd_image(KviKvsModuleCallbackCommandCall * c)
784 {
785 	QString szType, szCaption, szInitialSelection;
786 	kvs_uint_t iMaxSize;
787 	KviKvsVariantList params;
788 
789 	KVSM_PARAMETERS_BEGIN(c)
790 	KVSM_PARAMETER("mode", KVS_PT_STRING, 0, szType)
791 	KVSM_PARAMETER("caption", KVS_PT_STRING, 0, szCaption)
792 	KVSM_PARAMETER("initial_directory", KVS_PT_STRING, 0, szInitialSelection)
793 	KVSM_PARAMETER("maxsize", KVS_PT_UINT, KVS_PF_OPTIONAL, iMaxSize)
794 	KVSM_PARAMETER("magic", KVS_PT_VARIANTLIST, KVS_PF_OPTIONAL, params)
795 	KVSM_PARAMETERS_END(c)
796 	bool modal;
797 	if(c->hasSwitch('b', "modal"))
798 		modal = true;
799 	else
800 		modal = false;
801 	QString szCmd = c->callback()->code();
802 
803 	int iType = 0;
804 
805 	if(szType.contains('s'))
806 		iType |= KID_TYPE_BUILTIN_IMAGES_SMALL;
807 	if(szType.contains('f'))
808 		iType |= KID_TYPE_FULL_PATH;
809 	if(szType.isEmpty())
810 		iType = KID_TYPE_ALL;
811 
812 	if(iMaxSize < 1)
813 		iMaxSize = 256000;
814 
815 	KviKvsCallbackImageDialog * box = new KviKvsCallbackImageDialog(szCaption, szInitialSelection, iType, iMaxSize, szCmd, &params, c->window(), modal);
816 
817 	box->show();
818 
819 	return true;
820 }
821 
822 /*
823 	@doc: dialog.yesno
824 	@type:
825 		function
826 	@title:
827 		$dialog.yesno
828 	@short:
829 		Shows a simple yes/no dialog
830 	@syntax:
831 		$dialog.yesno(<caption:string>,<szText:string>)
832 	@description:
833 		Shows a simple yes/no dialog. Returns 1 if user clicks [i]Yes[/i] and 0 if (s)he clicks [i]No[/i].
834 		Please note that this dialog is [b]blocking[/b]: it blocks execution of the script
835 		until the user has selected either YES or NO.
836 	@examples:
837 	@seealso:
838 */
839 
dialog_kvs_fnc_yesno(KviKvsModuleFunctionCall * c)840 static bool dialog_kvs_fnc_yesno(KviKvsModuleFunctionCall * c)
841 {
842 	QString szCaption;
843 	QString szText;
844 	KVSM_PARAMETERS_BEGIN(c)
845 	KVSM_PARAMETER("caption", KVS_PT_STRING, 0, szCaption)
846 	KVSM_PARAMETER("text", KVS_PT_STRING, 0, szText)
847 	KVSM_PARAMETERS_END(c)
848 
849 	c->enterBlockingSection();
850 	bool yes = KviMessageBox::yesNo(szCaption, szText); // this will happily crash on quit ?
851 	if(!c->leaveBlockingSection())
852 		return true; // just die
853 	c->returnValue()->setBoolean(yes);
854 	return true;
855 }
856 
857 /*
858 	@doc: noblockingdialogs
859 	@type:
860 		generic
861 	@title:
862 		Why are there no blocking dialogs in KVIrc?
863 	@short:
864 		Technical answer
865 	@description:
866 		Why are there no blocking dialogs in KVIrc?[br]
867 		The answer is simple: because they're more confusing and tricky than it seems.[br]
868 		Blocking the entire program control flow while showing a dialog is
869 		rather a bad idea since we have to deal with external entities (servers and other users)
870 		that are [b]not[/b] blocked. This means that the blocking dialogs must block only the
871 		script control-flow but let the rest of the application running.
872 		Such blocking dialogs actually seem to simplify scripting because
873 		the programmer [i]feels[/i] that the control is always left in the script snippet that he is writing.
874 		This is actually confusing: the control IS in the script snippet but while the dialog
875 		is open the whole world can change: you can return from the dialog call and discover
876 		that the server connection no longer exists and the application is about to quit.[br]
877 		This may happen even with non-blocking dialogs,but in non-blocking mode you have
878 		a way to handle this event. Consider the following snippet of code:
879 		[example]
880 			echo My name is $?
881 		[/example]
882 		Where $? stands for a blocking input dialog that asks the user for some text.[br]
883 		When the input dialog returns the window that the echo was directed to no longer
884 		exists and you have no way to stop the echo! (Well... I could add extra code
885 		in the executable to handle all these situations but that would be really too expensive).[br]
886 		With object scripting this is actually dangerous: you might use a blocking dialog
887 		in an object signal handler and when returning discover that this object has been deleted!
888 		(The example refers to a simple object, but think about a complex hierarchy of objects
889 		where one random gets deleted...).[br]
890 		This is why the dialogs in KVIrc are non-blocking :)[br]
891 		That's REAL programming.
892 */
893 
dialog_module_init(KviModule * m)894 static bool dialog_module_init(KviModule * m)
895 {
896 	g_pDialogModuleDialogList = new KviPointerList<QWidget>;
897 	g_pDialogModuleDialogList->setAutoDelete(false);
898 
899 	KVSM_REGISTER_CALLBACK_COMMAND(m, "message", dialog_kvs_cmd_message);
900 	KVSM_REGISTER_CALLBACK_COMMAND(m, "textinput", dialog_kvs_cmd_textinput);
901 	KVSM_REGISTER_CALLBACK_COMMAND(m, "file", dialog_kvs_cmd_file);
902 	KVSM_REGISTER_CALLBACK_COMMAND(m, "image", dialog_kvs_cmd_image);
903 	KVSM_REGISTER_FUNCTION(m, "yesno", dialog_kvs_fnc_yesno);
904 
905 	return true;
906 }
907 
dialog_module_cleanup(KviModule *)908 static bool dialog_module_cleanup(KviModule *)
909 {
910 	// Here we get a tragedy if g_iLocalEventLoops > 0!
911 	while(g_pDialogModuleDialogList->first())
912 		delete g_pDialogModuleDialogList->first();
913 	delete g_pDialogModuleDialogList;
914 	g_pDialogModuleDialogList = nullptr;
915 	return true;
916 }
917 
dialog_module_can_unload(KviModule *)918 static bool dialog_module_can_unload(KviModule *)
919 {
920 	return g_pDialogModuleDialogList->isEmpty();
921 }
922 
923 KVIRC_MODULE(
924     "KVIrc script dialogs",
925     "4.0.0",
926     "Szymon Stefanek <pragma at kvirc dot net>",
927     "Adds the /dialog.* commands functionality\n",
928     dialog_module_init,
929     dialog_module_can_unload,
930     0,
931     dialog_module_cleanup,
932     0)
933