501ee605e41ff8f5f663c2fe57ccb2b4b1845a72
[quassel.git] / src / uisupport / inputline.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005/06 by the Quassel Project                          *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21 #include <QApplication>
22 #include <QMenu>
23 #include <QMessageBox>
24
25 #include "bufferview.h"
26 #include "graphicalui.h"
27 #include "inputline.h"
28 #include "tabcompleter.h"
29
30 const int leftMargin = 3;
31
32 InputLine::InputLine(QWidget *parent)
33   :
34 #ifdef HAVE_KDE
35     KTextEdit(parent),
36 #else
37     QTextEdit(parent),
38 #endif
39     idx(0),
40     tabCompleter(new TabCompleter(this))
41 {
42   // Make the QTextEdit look like a QLineEdit
43 #if QT_VERSION >= 0x040500
44   document()->setDocumentMargin(0); // new in Qt 4.5 and we really don't want it here
45 #endif
46   setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
47   setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
48   setAcceptRichText(false);
49   setLineWrapMode(NoWrap);
50 #ifdef HAVE_KDE
51   enableFindReplace(false);
52 #endif
53   resetLine();
54
55   connect(this, SIGNAL(textChanged()), this, SLOT(on_textChanged()));
56   connect(this, SIGNAL(returnPressed()), this, SLOT(on_returnPressed()));
57   connect(this, SIGNAL(textChanged(QString)), this, SLOT(on_textChanged(QString)));
58 }
59
60 InputLine::~InputLine() {
61 }
62
63 void InputLine::setCustomFont(const QFont &font) {
64   setFont(font);
65 }
66
67 QSize InputLine::sizeHint() const {
68   // use the style to determine a decent size
69   QFontMetrics fm(font());
70   int h = fm.lineSpacing() + 2 * frameWidth();
71   QStyleOptionFrameV2 opt;
72   opt.initFrom(this);
73   opt.rect = QRect(0, 0, 100, h);
74   opt.lineWidth = lineWidth();
75   opt.midLineWidth = midLineWidth();
76   opt.state |= QStyle::State_Sunken;
77   QSize s = style()->sizeFromContents(QStyle::CT_LineEdit, &opt, QSize(100, h).expandedTo(QApplication::globalStrut()), this);
78   return s;
79 }
80
81 QSize InputLine::minimumSizeHint() const {
82   return sizeHint();
83 }
84
85 bool InputLine::eventFilter(QObject *watched, QEvent *event) {
86   if(event->type() != QEvent::KeyPress)
87     return false;
88
89   // keys from BufferView should be sent to (and focus) the input line
90   BufferView *view = qobject_cast<BufferView *>(watched);
91   if(view) {
92     QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
93     if(keyEvent->text().length() == 1 && !(keyEvent->modifiers() & (Qt::ControlModifier ^ Qt::AltModifier)) ) { // normal key press
94       QChar c = keyEvent->text().at(0);
95       if(c.isLetterOrNumber() || c.isSpace() || c.isPunct() || c.isSymbol()) {
96         setFocus();
97         keyPressEvent(keyEvent);
98         return true;
99       } else
100         return false;
101     }
102   }
103   return false;
104 }
105
106 void InputLine::keyPressEvent(QKeyEvent * event) {
107   if(event->matches(QKeySequence::Find)) {
108     QAction *act = GraphicalUi::actionCollection()->action("ToggleSearchBar");
109     if(act) {
110       act->toggle();
111       event->accept();
112       return;
113     }
114   }
115
116   switch(event->key()) {
117   case Qt::Key_Up:
118     event->accept();
119
120     addToHistory(text(), true);
121
122     if(idx > 0) {
123       idx--;
124       showHistoryEntry();
125     }
126
127     break;
128
129   case Qt::Key_Down:
130     event->accept();
131
132     addToHistory(text(), true);
133
134     if(idx < history.count()) {
135       idx++;
136       if(idx < history.count() || tempHistory.contains(idx)) // tempHistory might have an entry for idx == history.count() + 1
137         showHistoryEntry();
138       else
139         resetLine();              // equals clear() in this case
140     } else {
141       addToHistory(text());
142       resetLine();
143     }
144
145     break;
146
147   case Qt::Key_Return:
148   case Qt::Key_Enter:
149   case Qt::Key_Select:
150     event->accept();
151     emit returnPressed();
152     break;
153
154   default:
155     QTextEdit::keyPressEvent(event);
156   }
157 }
158
159 bool InputLine::addToHistory(const QString &text, bool temporary) {
160   if(text.isEmpty())
161     return false;
162
163   Q_ASSERT(0 <= idx && idx <= history.count());
164
165   if(temporary) {
166     // if an entry of the history is changed, we remember it and show it again at this
167     // position until a line was actually sent
168     // sent lines get appended to the history
169     if(history.isEmpty() || text != history[idx - (int)(idx == history.count())]) {
170       tempHistory[idx] = text;
171       return true;
172     }
173   } else {
174     if(history.isEmpty() || text != history.last()) {
175       history << text;
176       tempHistory.clear();
177       return true;
178     }
179   }
180   return false;
181 }
182
183 void InputLine::on_returnPressed() {
184   if(!text().isEmpty()) {
185     addToHistory(text());
186     emit sendText(text());
187     resetLine();
188   }
189 }
190
191 void InputLine::on_textChanged(QString newText) {
192   QStringList lineSeparators;
193   lineSeparators << QString("\r\n")
194                  << QString('\n')
195                  << QString('\r');
196
197   QString lineSep;
198   foreach(QString separator, lineSeparators) {
199     if(newText.contains(separator)) {
200       lineSep = separator;
201       break;
202     }
203   }
204
205   if(lineSep.isEmpty())
206     return;
207
208   QStringList lines = newText.split(lineSep, QString::SkipEmptyParts);
209
210   if(lines.count() >= 4) {
211     QString msg = tr("Do you really want to paste %n lines?", "", lines.count());
212     msg += "<p>";
213     for(int i = 0; i < 3; i++) {
214       msg += lines[i].left(40);
215       if(lines[i].count() > 40)
216         msg += "...";
217       msg += "<br />";
218     }
219     msg += "...</p>";
220     QMessageBox question(QMessageBox::NoIcon, tr("Paste Protection"), msg, QMessageBox::Yes|QMessageBox::No);
221     question.setDefaultButton(QMessageBox::No);
222 #ifdef Q_WS_MAC
223     question.setWindowFlags(question.windowFlags() | Qt::Sheet); // Qt::Sheet is not ignored on other platforms as it should :/
224 #endif
225     if(question.exec() == QMessageBox::No) {
226       clear();
227       return;
228     }
229   }
230
231   foreach(QString line, lines) {
232     if(!line.isEmpty()) {
233       resetLine();
234       insert(line);
235       emit returnPressed();
236     }
237   }
238
239 //   if(newText.contains(lineSep)) {
240 //     clear();
241 //     QString line = newText.section(lineSep, 0, 0);
242 //     QString remainder = newText.section(lineSep, 1);
243 //     insert(line);
244 //     emit returnPressed();
245 //     insert(remainder);
246 //   }
247 }
248
249 void InputLine::resetLine() {
250   // every time the InputLine is cleared we also reset history index
251   idx = history.count();
252   clear();
253   QTextBlockFormat format = textCursor().blockFormat();
254   format.setLeftMargin(leftMargin); // we want a little space between the frame and the contents
255   textCursor().setBlockFormat(format);
256 }
257
258 void InputLine::showHistoryEntry() {
259   // if the user changed the history, display the changed line
260   setPlainText(tempHistory.contains(idx) ? tempHistory[idx] : history[idx]);
261   QTextCursor cursor = textCursor();
262   QTextBlockFormat format = cursor.blockFormat();
263   format.setLeftMargin(leftMargin); // we want a little space between the frame and the contents
264   cursor.setBlockFormat(format);
265   cursor.movePosition(QTextCursor::End);
266   setTextCursor(cursor);
267 }