Focus the message input when the chat window is activated
This commit is contained in:
1 parent
4e3c1182db
commit
d8db018166
5 files changed
+86
No files matched your search
@@ -1361,6 +1361,26 @@ void MainWindow::showIncomingMessageNotification(const QString& target, quint64
|
||||
m_messageNotification->showMessage(target, m_db->getDisplayName(author), channel, m_trayIcon->geometry(), screen());
|
||||
}
|
||||
|
||||
void MainWindow::changeEvent(QEvent* event) {
|
||||
QMainWindow::changeEvent(event);
|
||||
if (event->type() != QEvent::ActivationChange || !isActiveWindow()) return;
|
||||
QTimer::singleShot(0, this, [this] {
|
||||
auto* input = m_messageList ? m_messageList->focusProxy() : nullptr;
|
||||
const bool ready = input && input->isVisible() && input->isEnabled();
|
||||
if (QApplication::activeWindow() != this || !isVisible() || isMinimized() ||
|
||||
QApplication::activeModalWidget() || QApplication::activePopupWidget() || !ready) {
|
||||
GUI_DEBUG("window activation: input focus skipped active=%d visible=%d minimized=%d modal=%d popup=%d input_ready=%d",
|
||||
QApplication::activeWindow() == this, isVisible(), isMinimized(), bool(QApplication::activeModalWidget()),
|
||||
bool(QApplication::activePopupWidget()), ready);
|
||||
return;
|
||||
}
|
||||
m_messageList->setFocus(Qt::ActiveWindowFocusReason);
|
||||
auto* focused = QApplication::focusWidget();
|
||||
GUI_DEBUG("window activation: message input focused target=%s focus_widget=%s", qPrintable(m_currentChannelId),
|
||||
focused ? focused->metaObject()->className() : "none");
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent *event) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "MainWindow: closeEvent — hiding to tray");
|
||||
saveWindowGeometry();
|
||||
|
||||
@@ -53,6 +53,8 @@ public:
|
||||
~MainWindow() override;
|
||||
|
||||
protected:
|
||||
// После активации окна передать фокус composer; Qt сначала завершает восстановление фокуса дочерних виджетов.
|
||||
void changeEvent(QEvent* event) override;
|
||||
// Сохранить геометрию и скрыть в tray; для выхода служит requestShutdown.
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
|
||||
@@ -658,6 +658,7 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
|
||||
layout->addWidget(m_emojiPanel);
|
||||
|
||||
m_inputBar = new InputBar(m_emojiPanel, recorder, this);
|
||||
setFocusProxy(m_inputBar);
|
||||
layout->addWidget(m_inputBar);
|
||||
m_inputBar->installFileDropTarget(m_view->viewport());
|
||||
m_inputBar->focusProxy()->installEventFilter(m_view);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* История и composer выбранного канала/PM, read/scroll, загрузки, feedback, рация и каталог streams.
|
||||
* setFocus передаёт фокус полю сообщения через focusProxy composer.
|
||||
* Все методы/callbacks — GUI. Сетевые операции передаются мосту с владением запросом и привязкой instance.
|
||||
* Модель держит самостоятельные Qt-значения; DbManager/AudioRecorder заимствованы.
|
||||
* PM снимки проверяются по conv/generation, канальные строки — по message_id/version.
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
#include <QTemporaryDir>
|
||||
#include <QFile>
|
||||
#include <QTextEdit>
|
||||
#include <QLineEdit>
|
||||
#include <QRadioButton>
|
||||
#include <QClipboard>
|
||||
#include <QDialog>
|
||||
#include <QMenu>
|
||||
#include <QListView>
|
||||
#include <QStandardItemModel>
|
||||
#include <QPainter>
|
||||
@@ -192,6 +197,63 @@ private slots:
|
||||
QCOMPARE(outstanding.size(), size_t(0));
|
||||
#endif
|
||||
}
|
||||
void chatWindowReactivationFocus_data() {
|
||||
QTest::addColumn<bool>("imageChoice");
|
||||
QTest::newRow("history") << false; QTest::newRow("image-format") << true;
|
||||
}
|
||||
void chatWindowReactivationFocus() {
|
||||
QFETCH(bool, imageChoice); QTemporaryDir directory; QVERIFY(directory.isValid());
|
||||
MainWindow window(nullptr, nullptr, directory.filePath("focus.cfg"));
|
||||
auto* history = window.findChild<MessageList*>(); QVERIFY(history); history->loadChannel("123");
|
||||
auto* composer = history->findChild<InputBar*>(); QVERIFY(composer); composer->setRecordingContext(directory.path(), "123");
|
||||
auto* editor = composer->findChild<QTextEdit*>(); QVERIFY(editor); QCOMPARE(history->focusProxy(), composer);
|
||||
window.show(); window.activateWindow(); QTRY_VERIFY(window.isActiveWindow()); QTRY_VERIFY(editor->hasFocus());
|
||||
const QString draft = "first line\nsecond line"; editor->setPlainText(draft);
|
||||
auto cursor = editor->textCursor(); cursor.setPosition(2); cursor.setPosition(7, QTextCursor::KeepAnchor); editor->setTextCursor(cursor);
|
||||
QRadioButton* jpeg = nullptr;
|
||||
const auto clearClipboard = qScopeGuard([&] { if (imageChoice) QApplication::clipboard()->clear(); });
|
||||
if (imageChoice) {
|
||||
QImage image(32, 24, QImage::Format_RGB32); image.fill(Qt::green); QApplication::clipboard()->setImage(image); editor->paste();
|
||||
jpeg = composer->findChild<QRadioButton*>("clipboardJpegChoice"); QVERIFY(jpeg);
|
||||
QTRY_VERIFY(jpeg->isVisible()); QTest::mouseClick(jpeg, Qt::LeftButton); QVERIFY(jpeg->isChecked()); jpeg->setFocus();
|
||||
QTRY_VERIFY(jpeg->hasFocus());
|
||||
} else {
|
||||
auto* view = history->findChild<QListView*>(); QVERIFY(view); view->setFocus(); QTRY_VERIFY(view->hasFocus());
|
||||
}
|
||||
QLineEdit other; other.show(); other.activateWindow(); other.setFocus();
|
||||
QTRY_VERIFY(other.isActiveWindow()); QTRY_VERIFY(other.hasFocus());
|
||||
QTest::keyClicks(&other, "other window"); QCOMPARE(other.text(), QString("other window"));
|
||||
window.activateWindow(); QTRY_VERIFY(window.isActiveWindow()); QTRY_VERIFY(editor->hasFocus());
|
||||
QCOMPARE(editor->toPlainText(), draft); QCOMPARE(editor->textCursor().position(), 7); QCOMPARE(editor->textCursor().anchor(), 2);
|
||||
if (imageChoice) QVERIFY(jpeg->isChecked());
|
||||
QTest::keyClicks(QApplication::focusWidget(), "x"); QCOMPARE(editor->toPlainText(), QString("fixine\nsecond line"));
|
||||
}
|
||||
void chatActivationRespectsCurrentWindow_data() {
|
||||
QTest::addColumn<QString>("state");
|
||||
for (const char* state : {"other-window", "disabled", "hidden", "dialog", "popup"}) QTest::newRow(state) << QString(state);
|
||||
}
|
||||
void chatActivationRespectsCurrentWindow() {
|
||||
QFETCH(QString, state); QTemporaryDir directory; QVERIFY(directory.isValid());
|
||||
MainWindow window(nullptr, nullptr, directory.filePath("focus.cfg"));
|
||||
auto* history = window.findChild<MessageList*>(); QVERIFY(history); history->loadChannel("123");
|
||||
auto* composer = history->findChild<InputBar*>(); QVERIFY(composer);
|
||||
auto* editor = composer->findChild<QTextEdit*>(); QVERIFY(editor);
|
||||
auto* view = history->findChild<QListView*>(); QVERIFY(view);
|
||||
window.show(); window.activateWindow(); QTRY_VERIFY(window.isActiveWindow()); QTRY_VERIFY(editor->hasFocus());
|
||||
view->setFocus(); QTRY_VERIFY(view->hasFocus());
|
||||
QEvent activation(QEvent::ActivationChange); QApplication::sendEvent(&window, &activation);
|
||||
QLineEdit other; QDialog dialog(&window); QLineEdit dialogInput(&dialog); QMenu popup(&window); popup.addAction("Menu item");
|
||||
if (state == "other-window") { other.show(); other.activateWindow(); other.setFocus(); QTRY_VERIFY(other.isActiveWindow()); }
|
||||
if (state == "disabled") composer->setEnabled(false);
|
||||
if (state == "hidden") window.hide();
|
||||
if (state == "dialog") { dialog.setModal(true); dialog.show(); dialog.activateWindow(); dialogInput.setFocus(); }
|
||||
if (state == "popup") popup.popup(window.mapToGlobal(QPoint(100, 100)));
|
||||
QCoreApplication::processEvents(); QVERIFY(!editor->hasFocus());
|
||||
if (state == "other-window") { QVERIFY(other.isActiveWindow()); QVERIFY(other.hasFocus()); }
|
||||
if (state == "dialog") { QCOMPARE(QApplication::activeModalWidget(), &dialog); QTRY_VERIFY(dialogInput.hasFocus()); }
|
||||
if (state == "popup") QCOMPARE(QApplication::activePopupWidget(), &popup);
|
||||
}
|
||||
|
||||
void imagePreviewPreparedOutsidePaint() {
|
||||
QTemporaryDir directory; QVERIFY(directory.isValid());
|
||||
const QString path = directory.filePath("image.png");
|
||||
|
||||
Reference in new issue
Block a user