57
80
void ChatView::keyPressEvent(QKeyEvent *e)
59
if(e->key() == Key_Escape)
83
QKeySequence k(e->key() + (e->modifiers() & ~Qt::KeypadModifier));
85
// Temporary workaround for what i think is a Qt bug
86
if(ShortcutManager::instance()->shortcuts("common.close").contains(k)
87
|| ShortcutManager::instance()->shortcuts("message.send").contains(k)) {
92
// Ignore registered key sequences (and pass them up)
93
foreach(QAction* act, dialog_->actions()) {
94
foreach(QKeySequence keyseq, act->shortcuts()) {
95
if(!keyseq.isEmpty() && keyseq.matches(k) == QKeySequence::ExactMatch) {
104
/* if(e->key() == Qt::Key_Escape)
62
else if(e->key() == Key_W && e->state() & ControlButton)
107
else if(e->key() == Qt::Key_W && e->modifiers() & Qt::ControlModifier)
109
else if(e->key() == Qt::Key_C && (e->state() & Qt::ControlButton) && !hasSelectedText())
65
else if(e->key() == Key_Return && ((e->state() & ControlButton) || (e->state() & AltButton)) )
67
else if(e->key() == Key_H && (e->state() & ControlButton))
69
else if(e->key() == Key_I && (e->state() & ControlButton))
71
else if(e->key() == Key_M && (e->state() & ControlButton)) // newline
113
else if(e->key() == Qt::Key_Return && ((e->modifiers() & Qt::ControlModifier) || (e->modifiers() & Qt::AltModifier)) )
115
else if(e->key() == Qt::Key_H && (e->modifiers() & Qt::ControlModifier))
117
else if(e->key() == Qt::Key_I && (e->modifiers() & Qt::ControlModifier))
119
/*else*/ if(e->key() == Qt::Key_M && (e->modifiers() & Qt::ControlModifier) && !isReadOnly()) // newline
73
else if(e->key() == Key_U && (e->state() & ControlButton))
121
/* else if(e->key() == Qt::Key_U && (e->modifiers() & Qt::ControlModifier) && !isReadOnly())
76
QTextEdit::keyPressEvent(e);
79
void ChatView::resizeEvent(QResizeEvent *e)
81
// This fixes flyspray #45
82
if(contentsY() == contentsHeight() - visibleHeight())
124
PsiTextView::keyPressEvent(e);
128
* Copies any selected text to the clipboard
129
* if autoCopy is enabled and ChatView is in read-only mode.
131
void ChatView::autoCopy()
133
if (isReadOnly() && option.autoCopy) {
139
* Handle KeyPress events that happen in ChatEdit widget. This is used
140
* to 'fix' the copy shortcut.
141
* \param object object that should receive the event
142
* \param event received event
143
* \param chatEdit pointer to the dialog's ChatEdit widget that receives user input
145
bool ChatView::handleCopyEvent(QObject *object, QEvent *event, ChatEdit *chatEdit)
147
if (object == chatEdit && event->type() == QEvent::KeyPress) {
148
QKeyEvent *e = (QKeyEvent *)event;
149
if ((e->key() == Qt::Key_C && (e->modifiers() & Qt::ControlModifier)) ||
150
(e->key() == Qt::Key_Insert && (e->modifiers() & Qt::ControlModifier)))
152
if (!chatEdit->textCursor().hasSelection() &&
153
this->textCursor().hasSelection())
164
void ChatView::appendText(const QString &text)
166
bool doScrollToBottom = atBottom();
168
// prevent scrolling back to selected text when
169
// restoring selection
170
int scrollbarValue = verticalScrollBar()->value();
172
PsiTextView::appendText(text);
174
if (doScrollToBottom)
85
QTextEdit::resizeEvent(e);
177
verticalScrollBar()->setValue(scrollbarValue);
89
Copies any selected text (from selection 0) to the clipboard
90
if option.autoCopy is TRUE, \a copyAvailable is TRUE
91
and ChatView is in read-only mode.
92
In any other case it does nothing.
94
This slot is connected with copyAvailable(bool) signal
95
in ChatView's constructor.
100
void ChatView::autoCopy(bool copyAvailable)
181
* \brief Common function for ChatDlg and GCMainDlg. FIXME: Extract common
182
* chat window from both dialogs and move this function to that class.
184
QString ChatView::formatTimeStamp(const QDateTime &time)
102
if ( isReadOnly() && copyAvailable && option.autoCopy ) {
186
// TODO: provide an option for user to customize
188
return QString().sprintf("%02d:%02d:%02d", time.time().hour(), time.time().minute(), time.time().second());;
107
191
//----------------------------------------------------------------------------
109
193
//----------------------------------------------------------------------------
110
ChatEdit::ChatEdit(QWidget *parent, const char *name)
111
: PsiTextView(parent, name)
194
ChatEdit::ChatEdit(QWidget *parent)
197
, check_spelling_(false)
198
, spellhighlighter_(0)
113
setWordWrap(QTextEdit::WidgetWidth);
200
setWordWrapMode(QTextOption::WordWrap);
201
setAcceptRichText(false);
115
203
setReadOnly(false);
116
204
setUndoRedoEnabled(true);
118
setTextFormat(PlainText);
119
206
setMinimumHeight(48);
208
previous_position_ = 0;
209
setCheckSpelling(checkSpellingGloballyEnabled());
210
connect(PsiOptions::instance(),SIGNAL(optionChanged(const QString&)),SLOT(optionsChanged()));
122
213
ChatEdit::~ChatEdit()
215
delete spellhighlighter_;
218
void ChatEdit::setDialog(QWidget* dialog)
223
QSize ChatEdit::sizeHint() const
225
return minimumSizeHint();
228
bool ChatEdit::checkSpellingGloballyEnabled()
230
return PsiOptions::instance()->getOption("options.ui.spell-check.enabled").toBool();
233
void ChatEdit::setCheckSpelling(bool b)
236
if (check_spelling_) {
237
if (!spellhighlighter_)
238
spellhighlighter_ = new SpellHighlighter(document());
241
delete spellhighlighter_;
242
spellhighlighter_ = 0;
126
246
bool ChatEdit::focusNextPrevChild(bool next)
131
251
void ChatEdit::keyPressEvent(QKeyEvent *e)
133
if(e->key() == Key_Escape || (e->key() == Key_W && e->state() & ControlButton))
135
else if(e->key() == Key_Return && ((e->state() & ControlButton) || (e->state() & AltButton)) )
137
else if(e->key() == Key_M && (e->state() & ControlButton)) // newline
254
QKeySequence k(e->key() + (e->modifiers() & ~Qt::KeypadModifier));
255
// Temporary workaround for what i think is a Qt bug
256
if(ShortcutManager::instance()->shortcuts("common.close").contains(k)
257
|| ShortcutManager::instance()->shortcuts("chat.send").contains(k)) {
262
// Ignore registered key sequences (and pass them up)
263
foreach(QAction* act, dialog_->actions()) {
264
foreach(QKeySequence keyseq, act->shortcuts()) {
265
if(!keyseq.isEmpty() && keyseq.matches(k) == QKeySequence::ExactMatch) {
273
/* if(e->key() == Qt::Key_Escape || (e->key() == Qt::Key_W && e->modifiers() & Qt::ControlModifier))
275
else if(e->key() == Qt::Key_Return &&
276
((e->modifiers() & Qt::ControlModifier)
278
|| (e->modifiers() & Qt::AltModifier)
282
else if(e->key() == Qt::Key_M && (e->modifiers() & Qt::ControlModifier)) // newline
139
else if(e->key() == Key_H && (e->state() & ControlButton)) // history
141
else if(e->key() == Key_S && (e->state() & AltButton))
143
else if(e->key() == Key_U && (e->state() & ControlButton))
284
else if(e->key() == Qt::Key_H && (e->modifiers() & Qt::ControlModifier)) // history
286
else if(e->key() == Qt::Key_S && (e->modifiers() & Qt::AltModifier))
288
else*/ if(e->key() == Qt::Key_U && (e->modifiers() & Qt::ControlModifier))
145
else if((e->key() == Key_Return || e->key() == Key_Enter) && !(e->state() & ShiftButton) && option.chatSoftReturn)
147
else if((e->key() == Key_PageUp || e->key() == Key_PageDown) && (e->state() & ShiftButton))
149
else if((e->key() == Key_PageUp || e->key() == Key_PageDown) && (e->state() & ControlButton))
151
else if(e->key() == Key_C && (e->state() & ControlButton) && !hasSelectedText())
290
/* else if((e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) && !((e->modifiers() & Qt::ShiftModifier) || (e->modifiers() & Qt::AltModifier)) && option.chatSoftReturn)
292
else if((e->key() == Qt::Key_PageUp || e->key() == Qt::Key_PageDown) && (e->modifiers() & Qt::ShiftModifier))
294
else if((e->key() == Qt::Key_PageUp || e->key() == Qt::Key_PageDown) && (e->modifiers() & Qt::ControlModifier))
297
else if (e->key() == Qt::Key_QuoteLeft && e->modifiers() == Qt::ControlModifier) {
154
303
QTextEdit::keyPressEvent(e);
308
* Work around Qt bug, that QTextEdit doesn't accept() the
309
* event, so it could result in another context menu popping
310
* out after the first one.
312
void ChatEdit::contextMenuEvent(QContextMenuEvent *e)
314
last_click_ = e->pos();
315
if (check_spelling_ && textCursor().selectedText().isEmpty() && SpellChecker::instance()->available()) {
316
// Check if the word under the cursor is misspelled
317
QTextCursor tc = cursorForPosition(last_click_);
318
tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
319
tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
320
QString selected_word = tc.selectedText();
321
if (!selected_word.isEmpty() && !SpellChecker::instance()->isCorrect(selected_word)) {
322
QList<QString> suggestions = SpellChecker::instance()->suggestions(selected_word);
323
if (!suggestions.isEmpty() || SpellChecker::instance()->writable()) {
325
if (!suggestions.isEmpty()) {
326
foreach(QString suggestion, suggestions) {
327
QAction* act_suggestion = spell_menu.addAction(suggestion);
328
connect(act_suggestion,SIGNAL(triggered()),SLOT(applySuggestion()));
330
spell_menu.addSeparator();
332
if (SpellChecker::instance()->writable()) {
333
QAction* act_add = spell_menu.addAction(tr("Add to dictionary"));
334
connect(act_add,SIGNAL(triggered()),SLOT(addToDictionary()));
336
spell_menu.exec(QCursor::pos());
344
QTextEdit::contextMenuEvent(e);
349
* \brief handles a click on a suggestion
350
* \param the action is just the container which holds the suggestion.
352
* This method is called by the framework whenever a user clicked on the child popupmenu
353
* to select a suggestion for a missspelled word. It exchanges the missspelled word with the
354
* suggestion which is the text of the QAction parameter.
356
void ChatEdit::applySuggestion()
358
QAction* act_suggestion = (QAction*) sender();
359
int current_position = textCursor().position();
362
QTextCursor tc = cursorForPosition(last_click_);
363
tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
364
tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
365
int old_length = tc.position() - tc.anchor();
366
tc.insertText(act_suggestion->text());
369
// Put the cursor where it belongs
370
int new_length = act_suggestion->text().length();
371
tc.setPosition(current_position - old_length + new_length);
376
* \brief handles a click on the add2dict action of the parent popupmenu
377
* \param Never used bool parameter
379
* The method sets the cursor to the last mouseclick position and looks for the word which is placed there.
380
* This word is than added to the dictionary of aspell.
382
void ChatEdit::addToDictionary()
384
QTextCursor tc = cursorForPosition(last_click_);
385
int current_position = textCursor().position();
387
// Get the selected word
388
tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
389
tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
390
SpellChecker::instance()->add(tc.selectedText());
392
// Put the cursor where it belongs
394
tc.setPosition(current_position);
398
void ChatEdit::optionsChanged()
400
setCheckSpelling(checkSpellingGloballyEnabled());
157
403
//----------------------------------------------------------------------------
159
405
//----------------------------------------------------------------------------
160
LineEdit::LineEdit( QWidget *parent, const char *name )
161
: ChatEdit( parent, name )
406
LineEdit::LineEdit( QWidget *parent)
163
lastSize = QSize( 0, 0 );
164
initialWindowGeometry = QRect( 0, 0, 0, 0 );
166
QWidget *topParent = topLevelWidget();
167
topParent->installEventFilter( this );
168
moveTo = QPoint(topParent->x(), topParent->y());
170
moveTimer = new QTimer( this );
171
connect( moveTimer, SIGNAL(timeout()), SLOT(checkMoved()) );
173
// LineEdit's size hint is to be vertically as small as possible
174
setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Maximum );
176
setWrapPolicy( AtWordOrDocumentBoundary ); // no need for horizontal scrollbar with this
177
setHScrollBarMode(QScrollView::AlwaysOff);
179
setMinimumHeight(-1);
181
connect( this, SIGNAL( textChanged() ), SLOT( recalculateSize() ) );
409
setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); // no need for horizontal scrollbar with this
410
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
411
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
415
connect(this, SIGNAL(textChanged()), SLOT(recalculateSize()));
184
418
LineEdit::~LineEdit()
189
* Returns true if the dialog could be automatically resized by LineEdit.
191
bool LineEdit::allowResize() const
193
QWidget *topParent = topLevelWidget();
195
QRect desktop = qApp->desktop()->availableGeometry( (QWidget *)topParent );
196
float desktopArea = desktop.width() * desktop.height();
197
float dialogArea = topParent->frameGeometry().width() * topParent->frameGeometry().height();
199
// maximized and large chat windows shoulnd't resize the dialog
200
if ( (dialogArea / desktopArea) > 0.9 )
207
* In this implementation, it is quivalent to sizeHint().
209
422
QSize LineEdit::minimumSizeHint() const
424
QSize sh = QTextEdit::minimumSizeHint();
425
sh.setHeight(fontMetrics().height() + 1);
426
sh += QSize(0, QFrame::lineWidth() * 2);
215
* All magic is contained within this function. It determines the possible maximum
216
* height, and controls the appearance of vertical scrollbar.
218
430
QSize LineEdit::sizeHint() const
220
if ( lastSize.width() != 0 && lastSize.height() != 0 )
223
lastSize.setWidth( QTextEdit::sizeHint().width() );
226
if ( paragraphs() > 0 ) {
227
for ( int i = 0; i < paragraphs(); i++ ) {
228
h += paragraphRect( i ).height();
232
QWidget *topParent = topLevelWidget();
233
QRect desktop = qApp->desktop()->availableGeometry( (QWidget *)topParent );
234
int dh = h - height();
236
bool showScrollBar = false;
238
// check that our dialog's height doesn't exceed the desktop's
239
if ( allowResize() && (topParent->frameGeometry().height() + dh) >= desktop.height() ) {
240
// handles the case when the dialog could be resized,
241
// but lineedit wants to occupy too much space, so we should limit it
242
h = desktop.height() - ( topParent->frameGeometry().height() - height() );
243
showScrollBar = true;
245
else if ( !allowResize() && (h > topParent->geometry().height()/2) ) {
246
// handles the case when the dialog could not be resized(i.e. it's maximized).
247
// in this case we limit maximum height of lineedit to the half of dialog's
249
h = topParent->geometry().height() / 2;
250
showScrollBar = true;
253
// enable vertical scrollbar only when we're surely in need for it
254
QTextEdit *textEdit = (QTextEdit *)this;
256
textEdit->setVScrollBarMode( AlwaysOn );
258
textEdit->setVScrollBarMode( AlwaysOff );
260
lastSize.setHeight( h );
265
* Handles automatic dialog resize.
432
QSize sh = QTextEdit::sizeHint();
433
sh.setHeight(int(document()->documentLayout()->documentSize().height()));
434
sh += QSize(0, QFrame::lineWidth() * 2);
435
((QTextEdit*)this)->setMaximumHeight(sh.height());
439
void LineEdit::resizeEvent(QResizeEvent* e)
441
ChatEdit::resizeEvent(e);
442
QTimer::singleShot(0, this, SLOT(updateScrollBar()));
267
445
void LineEdit::recalculateSize()
269
if ( !isUpdatesEnabled() )
272
QSize oldSize = lastSize;
273
lastSize = QSize( 0, 0 ); // force sizeHint() to update
274
QSize newSize = sizeHint();
276
if ( QABS(newSize.height() - oldSize.height()) > 1 ) {
277
if ( allowResize() ) {
278
parentWidget()->layout()->setEnabled( false ); // try to reduce some flicker
280
QWidget *topParent = topLevelWidget();
281
int dh = newSize.height() - oldSize.height();
283
// if we're going to shrink dialog considerably, minimum
284
// size will prevent us from doing it. Activating main
285
// layout after resize will reset minimum sizes to sensible values
286
topParent->setMinimumSize( 10, 10 );
288
topParent->resize( topParent->width(),
289
topParent->height() + dh );
291
bool canMove = dh > 0;
292
int newy = topParent->y();
294
// try to move window to its old position
295
if ( movedWindow() && dh < 0 ) {
296
newy = initialWindowGeometry.y();
300
// check, if we need to move dialog upper
301
QRect desktop = qApp->desktop()->availableGeometry( (QWidget *)topParent );
302
if ( canMove && ( newy + topParent->frameGeometry().height() >= desktop.bottom() ) ) {
303
// initialize default window position
304
if ( !movedWindow() ) {
305
initialWindowGeometry = topParent->frameGeometry();
308
newy = QMAX(0, desktop.bottom() - topParent->frameGeometry().height());
311
if ( canMove && newy != topParent->y() ) {
312
topParent->move( topParent->x(), newy );
313
moveTo = topParent->pos();
316
parentWidget()->layout()->setEnabled( true );
319
// issue a layout update
320
parentWidget()->layout()->activate();
324
void LineEdit::resizeEvent( QResizeEvent *e )
326
// issue a re-layout, just in case
327
lastSize = QSize( 0, 0 ); // force sizeHint() to update
328
sizeHint(); // update the size hint, and cache the value
329
topLevelWidget()->layout()->activate();
331
PsiTextView::resizeEvent( e );
334
void LineEdit::setUpdatesEnabled( bool enable )
336
bool ue = isUpdatesEnabled();
337
ChatEdit::setUpdatesEnabled( enable );
343
bool LineEdit::eventFilter(QObject *watched, QEvent *e)
345
if ( !parentWidget()->layout()->isEnabled() )
346
return ChatEdit::eventFilter( watched, e );
348
if ( e->type() == QEvent::Reparent ) {
349
// In case of tabbed chats, dialog could be reparented to a higher-level dialog
350
// we need to get move events from it too. And unnecessary event filters
351
// are automatically cleaned up by Qt.
352
topLevelWidget()->installEventFilter( this );
354
else if ( e->type() == QEvent::Move ) {
355
QWidget *topParent = topLevelWidget();
356
if ( watched == topParent ) {
357
moveTimer->start( 100, true );
361
return ChatEdit::eventFilter( watched, e );
364
//! This function serves as a workaround for multiple move events, some of which
365
//! have incorrect coordinates (at least on KDE)
366
void LineEdit::checkMoved()
368
QWidget *topParent = topLevelWidget();
369
if ( QABS(moveTo.x() - topParent->x()) > 1 ||
370
QABS(moveTo.y() - topParent->y()) > 1 ) {
371
moveTo = topParent->pos();
372
initialWindowGeometry = QRect( 0, 0, 0, 0 );
376
bool LineEdit::movedWindow() const
378
return initialWindowGeometry.left() ||
379
initialWindowGeometry.top() ||
380
initialWindowGeometry.width() ||
381
initialWindowGeometry.height();
448
QTimer::singleShot(0, this, SLOT(updateScrollBar()));
451
void LineEdit::updateScrollBar()
453
setVerticalScrollBarPolicy(sizeHint().height() > height() ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff);
454
ensureCursorVisible();