1 //=============================================================================
2 //
3 // File : KviInput.cpp
4 // Creation date : Sun Jan 3 1999 23:11:50 by Szymon Stefanek
5 //
6 // This file is part of the KVIrc IRC client distribution
7 // Copyright (C) 1999 Szymon Stefanek (pragma at kvirc dot net)
8 // Copyright (C) 2008 Elvio Basello (hellvis69 at netsons dot org)
9 //
10 // This program is FREE software. You can redistribute it and/or
11 // modify it under the terms of the GNU General Public License
12 // as published by the Free Software Foundation; either version 2
13 // of the License, or (at your option) any later version.
14 //
15 // This program is distributed in the HOPE that it will be USEFUL,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 // See the GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with this program. If not, write to the Free Software Foundation,
22 // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 //
24 //=============================================================================
25
26 #define _KVI_DEBUG_CHECK_RANGE_
27 #include "kvi_debug.h"
28
29 #define _KVI_INPUT_CPP_
30
31 #include "KviInput.h"
32 #include "KviInputEditor.h"
33 #include "KviOptions.h"
34 #include "KviApplication.h"
35 #include "KviColorSelectionWindow.h"
36 #include "KviTextIconWindow.h"
37 #include "KviWindow.h"
38 #include "KviLocale.h"
39 #include "KviScriptEditor.h"
40 #include "KviHistoryWindow.h"
41 #include "KviUserInput.h"
42 #include "KviShortcut.h"
43 #include "KviTalHBox.h"
44 #include "KviTalToolTip.h"
45
46 #include <QLabel>
47 #include <QFileDialog>
48 #include <QPainter>
49 #include <QClipboard>
50 #include <QStringList>
51 #include <QApplication>
52 #include <QMessageBox>
53 #include <QLayout>
54 #include <QStyle>
55 #include <QEvent>
56 #include <QMouseEvent>
57 #include <QUrl>
58 #include <QHBoxLayout>
59 #include <QMenu>
60 #include <QPushButton>
61 #include <QFontMetrics>
62
63 #include <cctype>
64 #include <cstdlib>
65
66 //This comes from KviApplication.cpp
67 extern KviColorWindow * g_pColorWindow;
68 extern KviTextIconWindow * g_pTextIconWindow;
69 extern KviHistoryWindowWidget * g_pHistoryWindow;
70 extern QMenu * g_pInputPopup;
71
KviInput(KviWindow * pPar,KviUserListView * pView)72 KviInput::KviInput(KviWindow * pPar, KviUserListView * pView)
73 : QWidget(pPar)
74 {
75 setObjectName("input_widget");
76 m_pLayout = new QGridLayout(this);
77
78 m_pLayout->setMargin(0);
79 m_pLayout->setSpacing(0);
80
81 m_pWindow = pPar;
82 m_pMultiLineEditor = nullptr;
83
84 m_pHideToolsButton = new QToolButton(this);
85 m_pHideToolsButton->setObjectName("hide_container_button");
86
87 m_pHideToolsButton->setIconSize(QSize(22, 22));
88 m_pHideToolsButton->setFixedWidth(16);
89
90 if(g_pIconManager->getBigIcon("kvi_horizontal_left.png"))
91 m_pHideToolsButton->setIcon(QIcon(*(g_pIconManager->getBigIcon("kvi_horizontal_left.png"))));
92
93 connect(m_pHideToolsButton, SIGNAL(clicked()), this, SLOT(toggleToolButtons()));
94
95 m_pButtonContainer = new KviTalHBox(this);
96 m_pButtonContainer->setSpacing(0);
97 m_pButtonContainer->setMargin(0);
98
99 m_pButtonContainer->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
100
101 m_pHistoryButton = new QToolButton(m_pButtonContainer);
102 m_pHistoryButton->setObjectName("historybutton");
103
104 m_pHistoryButton->setIconSize(QSize(22, 22));
105
106 QIcon is1;
107 if(KVI_OPTION_BOOL(KviOption_boolEnableInputHistory)) //G&N mar 2005
108 {
109 is1.addPixmap(*(g_pIconManager->getSmallIcon(KviIconManager::History)));
110 m_pHistoryButton->setIcon(is1);
111 KviTalToolTip::add(m_pHistoryButton, __tr2qs("Show history Ctrl+PageUp"));
112 connect(m_pHistoryButton, SIGNAL(clicked()), this, SLOT(historyButtonClicked()));
113 }
114 else
115 {
116 is1.addPixmap(*(g_pIconManager->getSmallIcon(KviIconManager::HistoryOff)));
117 m_pHistoryButton->setIcon(is1);
118 m_pHistoryButton->setEnabled(false);
119 KviTalToolTip::add(m_pHistoryButton, __tr2qs("Input history disabled"));
120 }
121
122 m_pIconButton = new QToolButton(m_pButtonContainer);
123 m_pIconButton->setObjectName("iconbutton");
124
125 m_pIconButton->setIconSize(QSize(22, 22));
126 QIcon is3;
127 is3.addPixmap(*(g_pIconManager->getSmallIcon(KviIconManager::BigGrin)));
128 m_pIconButton->setIcon(is3);
129 KviTalToolTip::add(m_pIconButton, __tr2qs("Show icons popup Alt+E<br>See also /help texticons"));
130 connect(m_pIconButton, SIGNAL(clicked()), this, SLOT(iconButtonClicked()));
131
132 m_pCommandlineModeButton = new QToolButton(m_pButtonContainer);
133 m_pCommandlineModeButton->setObjectName("commandlinemodebutton");
134
135 m_pCommandlineModeButton->setIconSize(QSize(22, 22));
136 m_pCommandlineModeButton->setCheckable(true);
137 QIcon is0;
138 is0.addPixmap(*(g_pIconManager->getSmallIcon(KviIconManager::SaySmile)), QIcon::Normal, QIcon::On);
139 is0.addPixmap(*(g_pIconManager->getSmallIcon(KviIconManager::SayKvs)), QIcon::Normal, QIcon::Off);
140 m_pCommandlineModeButton->setIcon(is0);
141 KviTalToolTip::add(m_pCommandlineModeButton, __tr2qs("User friendly command-line mode Ctrl+Y<br>See also /help commandline"));
142
143 if(KVI_OPTION_BOOL(KviOption_boolCommandlineInUserFriendlyModeByDefault))
144 m_pCommandlineModeButton->setChecked(true);
145
146 m_pMultiEditorButton = new QToolButton(m_pButtonContainer);
147 m_pMultiEditorButton->setObjectName("multieditorbutton");
148
149 m_pMultiEditorButton->setCheckable(true);
150 m_pMultiEditorButton->setIconSize(QSize(22, 22));
151 QIcon is2;
152 is2.addPixmap(*(g_pIconManager->getSmallIcon(KviIconManager::IrcView)), QIcon::Normal, QIcon::On);
153 is2.addPixmap(*(g_pIconManager->getSmallIcon(KviIconManager::Terminal)), QIcon::Normal, QIcon::Off);
154 m_pMultiEditorButton->setIcon(is2);
155
156 QString szTip = __tr2qs("Multi-line editor Alt+Return");
157 #ifdef COMPILE_ON_MAC
158 szTip.replace(QString("Alt+Return;"), QString("⌥↩"));
159 #endif
160 KviTalToolTip::add(m_pMultiEditorButton, szTip);
161
162 connect(m_pMultiEditorButton, SIGNAL(toggled(bool)), this, SLOT(multiLineEditorButtonToggled(bool)));
163
164 m_pInputEditor = new KviInputEditor(this, pPar, pView);
165 connect(m_pInputEditor, SIGNAL(enterPressed()), this, SLOT(inputEditorEnterPressed()));
166 m_pInputEditor->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Ignored));
167
168 m_pMultiEditorButton->setAutoRaise(true);
169 m_pCommandlineModeButton->setAutoRaise(true);
170 m_pIconButton->setAutoRaise(true);
171 m_pHistoryButton->setAutoRaise(true);
172 m_pHideToolsButton->setAutoRaise(true);
173
174 m_pLayout->addWidget(m_pHideToolsButton, 0, 2, 2, 1);
175 m_pLayout->addWidget(m_pButtonContainer, 0, 1, 2, 1);
176 m_pLayout->addWidget(m_pInputEditor, 0, 0, 2, 1);
177
178 installShortcuts();
179 }
180
~KviInput()181 KviInput::~KviInput()
182 {
183 if(m_pMultiLineEditor)
184 KviScriptEditor::destroyInstance(m_pMultiLineEditor);
185 }
186
isButtonsHidden()187 bool KviInput::isButtonsHidden()
188 {
189 return m_pButtonContainer->isHidden();
190 }
191
setButtonsHidden(bool bHidden)192 void KviInput::setButtonsHidden(bool bHidden)
193 {
194 if(!m_pHideToolsButton || !m_pButtonContainer)
195 return;
196 if(bHidden == m_pButtonContainer->isHidden())
197 return;
198 m_pButtonContainer->setHidden(bHidden);
199 QPixmap * pix = bHidden ? g_pIconManager->getBigIcon("kvi_horizontal_right.png") : g_pIconManager->getBigIcon("kvi_horizontal_left.png");
200 if(pix)
201 m_pHideToolsButton->setIcon(QIcon(*pix));
202 }
203
toggleToolButtons()204 void KviInput::toggleToolButtons()
205 {
206 setButtonsHidden(!isButtonsHidden());
207 }
208
inputEditorEnterPressed()209 void KviInput::inputEditorEnterPressed()
210 {
211 QString szText = m_pInputEditor->text();
212 KviUserInput::parse(szText, m_pWindow, QString(), m_pCommandlineModeButton->isChecked());
213 m_pInputEditor->setText("");
214 m_pInputEditor->clearUndoStack();
215 }
216
installShortcuts()217 void KviInput::installShortcuts()
218 {
219 KviShortcut::create(KVI_SHORTCUTS_INPUT_MULTILINE, this, SLOT(toggleMultiLine()), nullptr, Qt::WidgetWithChildrenShortcut);
220 KviShortcut::create(KVI_SHORTCUTS_INPUT_MULTILINE_2, this, SLOT(toggleMultiLine()), nullptr, Qt::WidgetWithChildrenShortcut);
221 }
222
keyPressEvent(QKeyEvent * e)223 void KviInput::keyPressEvent(QKeyEvent * e)
224 {
225 if(e->modifiers() & Qt::ControlModifier)
226 {
227 switch(e->key())
228 {
229 case Qt::Key_Enter:
230 case Qt::Key_Return:
231 {
232 if(m_pMultiLineEditor)
233 {
234 QString szText;
235 m_pMultiLineEditor->getText(szText);
236 if(szText.isEmpty())
237 return;
238 if(KVI_OPTION_BOOL(KviOption_boolWarnAboutPastingMultipleLines))
239 {
240 if(szText.length() > 256)
241 {
242 if(szText[0] != '/')
243 {
244 int nLines = szText.count('\n') + 1;
245 if(nLines > 15)
246 {
247 QMessageBox pMsgBox;
248 pMsgBox.setText(__tr2qs("You're about to send a message with %1 lines of text.<br><br>"
249 "This warning is here to prevent you from accidentally "
250 "pasting and sending a really large, potentially unedited message from your clipboard.<br><br>"
251 "Some IRC servers may also consider %1 lines of text a flood, "
252 "in which case you will be disconnected from said server.<br><br>"
253 "Do you still want the message to be sent?").arg(nLines));
254
255 pMsgBox.setWindowTitle(__tr2qs("Confirm Sending a Large Message - KVIrc"));
256 pMsgBox.setIcon(QMessageBox::Question);
257 QAbstractButton * pAlwaysButton = pMsgBox.addButton(__tr2qs("Always"), QMessageBox::YesRole);
258 /* QAbstractButton *pYesButton = */ pMsgBox.addButton(__tr2qs("Yes"), QMessageBox::YesRole);
259 QAbstractButton * pNoButton = pMsgBox.addButton(__tr2qs("No"), QMessageBox::NoRole);
260 pMsgBox.setDefaultButton(qobject_cast<QPushButton *>(pNoButton));
261 pMsgBox.exec();
262 if(pMsgBox.clickedButton() == pAlwaysButton)
263 {
264 KVI_OPTION_BOOL(KviOption_boolWarnAboutPastingMultipleLines) = false;
265 }
266 else if(pMsgBox.clickedButton() == pNoButton || pMsgBox.clickedButton() == nullptr)
267 {
268 return;
269 }
270 }
271 }
272 }
273 }
274 szText.replace('\t', QString(KVI_OPTION_UINT(KviOption_uintSpacesToExpandTabulationInput), ' ')); //expand tabs to spaces
275 KviUserInput::parse(szText, m_pWindow, QString(), m_pCommandlineModeButton->isChecked());
276 m_pMultiLineEditor->setText("");
277 }
278 }
279 break;
280 }
281 }
282 }
283
multiLinePaste(const QString & szText)284 void KviInput::multiLinePaste(const QString & szText)
285 {
286 if(!m_pMultiLineEditor)
287 multiLineEditorButtonToggled(true);
288 QString szCompleteText = m_pInputEditor->text();
289 szCompleteText.append(szText);
290 m_pMultiLineEditor->setText(szCompleteText);
291 }
292
multiLineEditorButtonToggled(bool bOn)293 void KviInput::multiLineEditorButtonToggled(bool bOn)
294 {
295 if(m_pMultiLineEditor)
296 {
297 if(bOn)
298 return;
299 QString szTmp;
300 m_pMultiLineEditor->getText(szTmp);
301 m_pLayout->removeWidget(m_pMultiLineEditor);
302 m_pLayout->removeWidget(m_pHelpLabel);
303
304 KviScriptEditor::destroyInstance(m_pMultiLineEditor);
305 m_pMultiLineEditor = nullptr;
306
307 delete m_pHelpLabel;
308 m_pHelpLabel = nullptr;
309
310 szTmp.replace(QRegExp("[\a\f\n\r\v]"), QString(" "));
311 szTmp.replace('\t', QString(KVI_OPTION_UINT(KviOption_uintSpacesToExpandTabulationInput), ' ')); //expand tabs to spaces
312 m_pInputEditor->setText(szTmp);
313 m_pInputEditor->show();
314 m_pWindow->childrenTreeChanged(nullptr);
315 m_pInputEditor->setFocus();
316 m_pMultiEditorButton->setChecked(false);
317 }
318 else
319 {
320 if(!bOn)
321 return;
322 m_pInputEditor->hide();
323
324 m_pHelpLabel = new QLabel();
325 m_pHelpLabel->setContentsMargins(15, 5, 0, 0);
326
327 QString tmpHelpLabel = __tr2qs("Ctrl+Return; submits contents, Alt+Return; hides this editor");
328 #ifdef COMPILE_ON_MAC
329 tmpHelpLabel.replace(QString("Ctrl+Return;"), QString("⌘↩"));
330 tmpHelpLabel.replace(QString("Alt+Return;"), QString("⌥↩"));
331 #endif
332 m_pHelpLabel->setText(tmpHelpLabel);
333 m_pLayout->addWidget(m_pHelpLabel, 0, 0, 1, 1);
334
335 m_pMultiLineEditor = KviScriptEditor::createInstance(this);
336 m_pMultiLineEditor->setText(m_pInputEditor->text());
337 m_pLayout->addWidget(m_pMultiLineEditor, 1, 0, 1, 1);
338
339 m_pWindow->childrenTreeChanged(m_pMultiLineEditor);
340 m_pMultiLineEditor->setFocus();
341 m_pMultiEditorButton->setChecked(true);
342 }
343 }
344
iconButtonClicked()345 void KviInput::iconButtonClicked()
346 {
347 if(!g_pTextIconWindow)
348 g_pTextIconWindow = new KviTextIconWindow();
349 QPoint pnt = m_pIconButton->mapToGlobal(QPoint(m_pIconButton->width(), 0));
350 g_pTextIconWindow->move(pnt.x() - g_pTextIconWindow->width(), pnt.y() - g_pTextIconWindow->height());
351 g_pTextIconWindow->popup(this, true);
352 }
353
historyButtonClicked()354 void KviInput::historyButtonClicked()
355 {
356 if(!g_pHistoryWindow)
357 g_pHistoryWindow = new KviHistoryWindowWidget();
358
359 QPoint pnt = mapToGlobal(QPoint(0, 0));
360
361 g_pHistoryWindow->setGeometry(pnt.x(), pnt.y() - KVI_HISTORY_WIN_HEIGHT, width(), KVI_HISTORY_WIN_HEIGHT);
362 g_pHistoryWindow->popup(this);
363 }
364
setFocus()365 void KviInput::setFocus()
366 {
367 // redirect setFocus() to the right children
368 if(m_pMultiLineEditor)
369 m_pMultiLineEditor->setFocus();
370 else
371 m_pInputEditor->setFocus();
372 }
373
focusInEvent(QFocusEvent *)374 void KviInput::focusInEvent(QFocusEvent *)
375 {
376 // if we get a focus in event, redirect the focus to the children
377 if(m_pMultiLineEditor)
378 m_pMultiLineEditor->setFocus();
379 else
380 m_pInputEditor->setFocus();
381 }
382
heightHint() const383 int KviInput::heightHint() const
384 {
385 return m_pMultiLineEditor ? (m_pInputEditor->heightHint() * 6) : m_pInputEditor->heightHint();
386 }
387
setText(const QString & szText)388 void KviInput::setText(const QString & szText)
389 {
390 if(m_pMultiLineEditor)
391 m_pMultiLineEditor->setText(szText);
392 else
393 m_pInputEditor->setText(szText);
394 }
395
insertChar(char c)396 void KviInput::insertChar(char c)
397 {
398 m_pInputEditor->insertChar(c);
399 }
400
insertText(const QString & szText)401 void KviInput::insertText(const QString & szText)
402 {
403 m_pInputEditor->insertText(szText);
404 }
405
applyOptions()406 void KviInput::applyOptions()
407 {
408 if(KVI_OPTION_BOOL(KviOption_boolEnableInputHistory))
409 {
410 if(!m_pHistoryButton->isEnabled())
411 {
412 QIcon is1;
413 is1.addPixmap(*(g_pIconManager->getSmallIcon(KviIconManager::History)));
414 m_pHistoryButton->setIcon(is1);
415 m_pHistoryButton->setEnabled(true);
416 KviTalToolTip::add(m_pHistoryButton, __tr2qs("Show history Ctrl+PageUp"));
417 connect(m_pHistoryButton, SIGNAL(clicked()), this, SLOT(historyButtonClicked()));
418 }
419 }
420 else
421 {
422 if(m_pHistoryButton->isEnabled())
423 {
424 QIcon is1;
425 is1.addPixmap(*(g_pIconManager->getSmallIcon(KviIconManager::HistoryOff)));
426 m_pHistoryButton->setIcon(is1);
427 m_pHistoryButton->setEnabled(false);
428 KviTalToolTip::add(m_pHistoryButton, __tr2qs("Input history disabled"));
429 m_pHistoryButton->disconnect(SIGNAL(clicked()));
430 }
431 }
432
433 m_pInputEditor->applyOptions();
434 }
435
setFocusProxy(QWidget *)436 void KviInput::setFocusProxy(QWidget *)
437 {
438 }/* do nothing */
439
text()440 QString KviInput::text()
441 {
442 QString szText;
443 if(m_pMultiLineEditor)
444 m_pMultiLineEditor->getText(szText);
445 else
446 szText = m_pInputEditor->text();
447 return szText;
448 }
449
toggleMultiLine()450 void KviInput::toggleMultiLine()
451 {
452 multiLineEditorButtonToggled(!m_pMultiLineEditor);
453 }
454
455 /*
456 @doc: commandline
457 @title:
458 The Commandline Input Features
459 @type:
460 generic
461 @short:
462 Commandline input features
463 @body:
464 [big]Principles of operation[/big]
465 [br]
466 The idea is simple: anything that starts with a slash [b]/[/b] character
467 is interpreted as a command. Anything else is plain text that is
468 sent to the target of the window (channel, query, DCC chat etc.).
469 [big]The two operating modes[/big]
470 [br]
471 The commandline input has two operating modes: the [i]user friendly mode[/i] and
472 the [i]KVS mode[/i]. In the user friendly mode all the parameters of the commands
473 are interpreted exactly like you type them. There is no special interpretation
474 of [b]$[/b], [b]%[/b], [b]-[/b], [b]([/b] and [b];[/b] characters. This allows you to type [i]/me is happy ;)[/i], for example.
475 In the KVS mode the full parameter interpretation is enabled and the commands
476 work just like in any other script editor. This means that anything that
477 starts with a [b]$[/b] is a function call, anything that starts with a % is a variable,
478 the dash characters after command names are interpreted as switches and [b];[/b] is the
479 command separator. This in turn does [b]not[/b] allow you to type [i]/me is happy ;)[/i]
480 because [b];[/b] is the command separator and ) will be interpreted as the beginning
481 of the next command. In KVS mode you obviously have to escape the ; character
482 by typing [i]/me is happy \;)[/i]. The user friendly mode is good for everyday chatting
483 and for novice users while the KVS mode is for experts that know that minimum about
484 scripting languages. Please note that in the user-friendly mode you're not allowed
485 to type multiple commands at once :).
486 [br]
487 Also look at the [doc:keyboard]keyboard shortcuts[/doc] reference.[br]
488 If you drop a file on this widget, a <a href="parse.kvihelp">/PARSE <filename></a> will be executed.[br]
489 You can enable word substitution in the preferences dialog.[br]
490 For example, if you choose to substitute [b]afaik[/b] with [b]A[/b]s [b]f[/b]ar [b]a[/b]s [b]I[/b] [b]k[/b]now,[br]
491 when you will type [b]afaik[/b] somewhere in the command line, and then
492 press Space or Return, that word will be replaced with [i]As far as I know[/i].[br]
493 Experiment with it :)[br]
494 The Tab key activates the completion of the current word.[br]
495 If a word is prefixed with a [b]/[/b], it is treated as a command to be completed,
496 if it begins with [b]$[/b], it is treated as a function or identifier to be completed,
497 otherwise it is treated as a nickname or filename to be completed.
498 [example]
499 /ec<Tab> will produce /echo<space>
500 /echo $loca<Tab> will produce /echo $localhost
501 [/example]
502 Multiple matches are listed in the view window and the word is completed
503 to the common part of all the matches.
504 [example]
505 $sel<Tab;> will find multiple matches and produce $selected
506 [/example]
507 Experiment with that too :)
508 [br]
509 */
510