manage conflicting hotkeys in QComboBox, QLineEdit, QSpinBox

This commit is contained in:
Garux 2022-12-20 23:41:16 +06:00
parent 88a03b07be
commit 2ed868d254
8 changed files with 145 additions and 25 deletions

41
libs/gtkutil/combobox.h Normal file
View File

@ -0,0 +1,41 @@
/*
Copyright (C) 2001-2006, William Joseph.
All Rights Reserved.
This file is part of GtkRadiant.
GtkRadiant is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
GtkRadiant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <QComboBox>
#include <QKeyEvent>
/// @brief Subclassed QComboBox not comsuming Enter key (why does it do it? works as expected for editable ComboBox)
/// purpose is to have working confirmation by Enter in dialogs
/// fixme unsolved crude problem here is triggering arrows, page, home, end global shortcuts when pressed in popup; even if modal dialog 😱
class ComboBox : public QComboBox
{
protected:
void keyPressEvent( QKeyEvent *event ) override {
if( event->key() == Qt::Key_Enter
|| event->key() == Qt::Key_Return ){
event->ignore();
return;
}
QComboBox::keyPressEvent( event );
}
};

54
libs/gtkutil/lineedit.h Normal file
View File

@ -0,0 +1,54 @@
/*
Copyright (C) 2001-2006, William Joseph.
All Rights Reserved.
This file is part of GtkRadiant.
GtkRadiant is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
GtkRadiant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <QLineEdit>
#include <QKeyEvent>
#include <QCompleter>
#include <QAbstractItemView>
/// @brief Subclassed QLineEdit not comsuming undo/redo shortcuts
/// it's more useful to have working undo of editor
/// Qt should better not eat these shortcuts when QLineEdit's undo is disabled, but it does
class LineEdit : public QLineEdit
{
protected:
bool event( QEvent *event ) override {
if( event->type() == QEvent::ShortcutOverride ){
QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
if( keyEvent == QKeySequence::StandardKey::Undo
|| keyEvent == QKeySequence::StandardKey::Redo )
return false;
// fix QCompleter leaking shortcuts
if( completer() != nullptr && completer()->popup() != nullptr && completer()->popup()->isVisible() )
if( keyEvent->key() == Qt::Key_Return
|| keyEvent->key() == Qt::Key_Enter
|| keyEvent->key() == Qt::Key_Escape
|| keyEvent->key() == Qt::Key_Up
|| keyEvent->key() == Qt::Key_Down
|| keyEvent->key() == Qt::Key_PageUp
|| keyEvent->key() == Qt::Key_PageDown )
event->accept();
}
return QLineEdit::event( event );
}
};

View File

@ -21,22 +21,20 @@
#pragma once #pragma once
#include <QLineEdit> #include "lineedit.h"
#include <QKeyEvent> #include "spinbox.h"
#include <QTimer> #include <QTimer>
#include "generic/callback.h" #include "generic/callback.h"
#include "spinbox.h"
class NonModalEntry : public LineEdit
class NonModalEntry : public QLineEdit
{ {
bool m_editing{}; bool m_editing{};
Callback m_apply; Callback m_apply;
Callback m_cancel; Callback m_cancel;
public: public:
NonModalEntry( const Callback& apply, const Callback& cancel ) : QLineEdit(), m_apply( apply ), m_cancel( cancel ){ NonModalEntry( const Callback& apply, const Callback& cancel ) : LineEdit(), m_apply( apply ), m_cancel( cancel ){
QObject::connect( this, &QLineEdit::textEdited, [this](){ m_editing = true; } ); QObject::connect( this, &QLineEdit::textEdited, [this](){ m_editing = true; } );
QObject::connect( this, &QLineEdit::editingFinished, [this](){ // on enter or focus out QObject::connect( this, &QLineEdit::editingFinished, [this](){ // on enter or focus out
if( m_editing ){ if( m_editing ){
@ -53,11 +51,12 @@ protected:
if( keyEvent->key() == Qt::Key_Escape ){ if( keyEvent->key() == Qt::Key_Escape ){
m_editing = false; m_editing = false;
m_cancel(); m_cancel();
clearFocus();
event->accept(); event->accept();
// defer clearFocus(); as immediately done after certain actions = cursor visible + not handling key input
QTimer::singleShot( 0, [this](){ clearFocus(); } );
} }
} }
return QLineEdit::event( event ); return LineEdit::event( event );
} }
void focusInEvent( QFocusEvent *event ) override { void focusInEvent( QFocusEvent *event ) override {
if( event->reason() == Qt::FocusReason::MouseFocusReason ) if( event->reason() == Qt::FocusReason::MouseFocusReason )

View File

@ -45,14 +45,23 @@ public:
} }
protected: protected:
bool event( QEvent *event ) override { bool event( QEvent *event ) override {
/* QAbstractSpinBox has no well defined ShortcutOverride routine, unlike underlying QLineEdit
thus box' portion of key events ends up firing application's shortcuts (e.g. up, down, pgUp, pgDown)
let's lock entire input on the box except a few keys, handled by parent window */
if( event->type() == QEvent::ShortcutOverride ){ if( event->type() == QEvent::ShortcutOverride ){
QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event ); QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
if( keyEvent->key() != Qt::Key_Return // don't pass undo/redo to be eaten by underlying lineedit
&& keyEvent->key() != Qt::Key_Enter // it's more useful to have working undo of editor
&& keyEvent->key() != Qt::Key_Escape ) if( keyEvent == QKeySequence::StandardKey::Undo
|| keyEvent == QKeySequence::StandardKey::Redo )
return false;
/* QAbstractSpinBox has no well defined ShortcutOverride routine, unlike underlying QLineEdit
thus box' portion of key events ends up firing application's shortcuts (e.g. up, down, pgUp, pgDown)
let's implement it kinda */
if( keyEvent->key() == Qt::Key_PageUp
|| keyEvent->key() == Qt::Key_PageDown
|| keyEvent->key() == Qt::Key_Up
|| keyEvent->key() == Qt::Key_Down
|| keyEvent->key() == Qt::Key_End
|| keyEvent->key() == Qt::Key_Home
|| keyEvent == QKeySequence::StandardKey::SelectAll )
event->accept(); event->accept();
} }
return SpinT::event( event ); return SpinT::event( event );

View File

@ -45,7 +45,7 @@
#include "gtkmisc.h" #include "gtkmisc.h"
#include <QCheckBox> #include <QCheckBox>
#include <QComboBox> #include "gtkutil/combobox.h"
#include <QSlider> #include <QSlider>
#include <QRadioButton> #include <QRadioButton>
#include <QButtonGroup> #include <QButtonGroup>
@ -411,7 +411,7 @@ QCheckBox* Dialog::addCheckBox( QGridLayout* grid, const char* name, const char*
} }
QComboBox* Dialog::addCombo( QGridLayout* grid, const char* name, StringArrayRange values, const IntImportCallback& importViewer, const IntExportCallback& exportViewer ){ QComboBox* Dialog::addCombo( QGridLayout* grid, const char* name, StringArrayRange values, const IntImportCallback& importViewer, const IntExportCallback& exportViewer ){
auto combo = new QComboBox; auto combo = new ComboBox;
for ( const char *value : values ) for ( const char *value : values )
combo->addItem( value ); combo->addItem( value );

View File

@ -44,7 +44,7 @@
#include <QGridLayout> #include <QGridLayout>
#include <QScrollArea> #include <QScrollArea>
#include <QCheckBox> #include <QCheckBox>
#include <QLineEdit> #include "gtkutil/lineedit.h"
#include <QLabel> #include <QLabel>
#include <QPushButton> #include <QPushButton>
#include <QToolButton> #include <QToolButton>
@ -1229,13 +1229,13 @@ QWidget* EntityInspector_constructWindow( QWidget* toplevel ){
grid->addWidget( new QLabel( "Value" ), 1, 0 ); grid->addWidget( new QLabel( "Value" ), 1, 0 );
} }
{ {
auto line = g_entityKeyEntry = new QLineEdit; auto line = g_entityKeyEntry = new LineEdit;
grid->addWidget( line, 0, 1 ); grid->addWidget( line, 0, 1 );
QObject::connect( line, &QLineEdit::returnPressed, [](){ g_entityValueEntry->setFocus(); g_entityValueEntry->selectAll(); } ); QObject::connect( line, &QLineEdit::returnPressed, [](){ g_entityValueEntry->setFocus(); g_entityValueEntry->selectAll(); } );
} }
{ {
auto line = g_entityValueEntry = new QLineEdit; auto line = g_entityValueEntry = new LineEdit;
grid->addWidget( line, 1, 1 ); grid->addWidget( line, 1, 1 );
QObject::connect( line, &QLineEdit::returnPressed, [](){ EntityInspector_applyKeyValue(); } ); QObject::connect( line, &QLineEdit::returnPressed, [](){ EntityInspector_applyKeyValue(); } );
} }

View File

@ -33,7 +33,7 @@
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QFormLayout> #include <QFormLayout>
#include <QLineEdit> #include "gtkutil/lineedit.h"
#include <QLabel> #include <QLabel>
#include <QCheckBox> #include <QCheckBox>
#include <QEvent> #include <QEvent>
@ -105,6 +105,21 @@ protected:
FindActiveTracker s_find_focus_in( true ); FindActiveTracker s_find_focus_in( true );
FindActiveTracker s_replace_focus_in( false ); FindActiveTracker s_replace_focus_in( false );
class : public QObject
{
protected:
bool eventFilter( QObject *obj, QEvent *event ) override {
if( event->type() == QEvent::ShortcutOverride ) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
if( keyEvent->key() == Qt::Key_Tab ){
event->accept();
}
}
return QObject::eventFilter( obj, event ); // standard event processing
}
}
s_pressedKeysFilter;
} }
@ -120,6 +135,8 @@ FindTextureDialog::~FindTextureDialog(){
void FindTextureDialog::BuildDialog(){ void FindTextureDialog::BuildDialog(){
GetWidget()->setWindowTitle( "Find / Replace Texture(s)" ); GetWidget()->setWindowTitle( "Find / Replace Texture(s)" );
GetWidget()->installEventFilter( &s_pressedKeysFilter );
g_guiSettings.addWindow( GetWidget(), "TextureBrowser/FindReplace" ); g_guiSettings.addWindow( GetWidget(), "TextureBrowser/FindReplace" );
auto hbox = new QHBoxLayout( GetWidget() ); auto hbox = new QHBoxLayout( GetWidget() );
@ -127,14 +144,14 @@ void FindTextureDialog::BuildDialog(){
hbox->addLayout( form ); hbox->addLayout( form );
{ {
auto entry = new QLineEdit; auto entry = new LineEdit;
form->addRow( "Find:", entry ); form->addRow( "Find:", entry );
AddDialogData( *entry, m_strFind ); AddDialogData( *entry, m_strFind );
entry->installEventFilter( &s_find_focus_in ); entry->installEventFilter( &s_find_focus_in );
GlobalTextureEntryCompletion::instance().connect( entry ); GlobalTextureEntryCompletion::instance().connect( entry );
} }
{ {
auto entry = new QLineEdit; auto entry = new LineEdit;
auto label = new QLabel( "Replace:" ); auto label = new QLabel( "Replace:" );
form->addRow( label, entry ); form->addRow( label, entry );
entry->setPlaceholderText( "Empty = search mode" ); entry->setPlaceholderText( "Empty = search mode" );

View File

@ -820,7 +820,7 @@ void Patch_constructMenu( QMenu* menu ){
#include "gtkutil/spinbox.h" #include "gtkutil/spinbox.h"
#include <QDialog> #include <QDialog>
#include <QComboBox> #include "gtkutil/combobox.h"
#include <QCheckBox> #include <QCheckBox>
#include <QFormLayout> #include <QFormLayout>
#include <QDialogButtonBox> #include <QDialogButtonBox>
@ -831,8 +831,8 @@ void DoNewPatchDlg( EPatchPrefab prefab, int minrows, int mincols, int defrows,
QDialog dialog( MainFrame_getWindow(), Qt::Dialog | Qt::WindowCloseButtonHint ); QDialog dialog( MainFrame_getWindow(), Qt::Dialog | Qt::WindowCloseButtonHint );
dialog.setWindowTitle( "Patch density" ); dialog.setWindowTitle( "Patch density" );
auto width = new QComboBox; auto width = new ComboBox;
auto height = new QComboBox; auto height = new ComboBox;
auto redisperseCheckBox = new QCheckBox( "Square" ); auto redisperseCheckBox = new QCheckBox( "Square" );
{ {