Files
wxWidgets/src/gtk/textentry.cpp
Yuri D'Elia f9b793f8d1 Check for valid entry in wxTextEntry::EnableTextChangedEvents()
GetTextObject() might return null, e.g. it does it for read-only
wxBitmapComboBox, so EnableTextChangedEvents() must account for this
possibility, as it's not really possible to avoid calling it in this
case, as it's called indirectly from e.g. SetSelection().

Check that the entry is valid before enabling or disabling events for it
to avoid several GLib assertion failures every time when e.g.
wxBitmapComboBox::SetValue() is called.

Closes https://github.com/wxWidgets/wxWidgets/pull/1756
2020-03-11 22:30:55 +01:00

1181 lines
34 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: src/gtk/textentry.cpp
// Purpose: wxTextEntry implementation for wxGTK
// Author: Vadim Zeitlin
// Created: 2007-09-24
// Copyright: (c) 2007 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
// ============================================================================
// declarations
// ============================================================================
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
// for compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#if wxUSE_TEXTCTRL || wxUSE_COMBOBOX
#ifndef WX_PRECOMP
#include "wx/event.h"
#include "wx/textentry.h"
#include "wx/textctrl.h"
#include "wx/window.h"
#endif //WX_PRECOMP
#include "wx/textcompleter.h"
#include "wx/gtk/private.h"
#include "wx/gtk/private/object.h"
#include "wx/gtk/private/string.h"
// ----------------------------------------------------------------------------
// wxTextCoalesceData
// ----------------------------------------------------------------------------
class wxTextCoalesceData
{
public:
wxTextCoalesceData(GtkWidget* widget, gulong handlerAfterKeyPress)
: m_handlerAfterKeyPress(handlerAfterKeyPress)
{
m_inKeyPress = false;
m_pendingTextChanged = false;
// This signal handler is unblocked in StartHandlingKeyPress(), so
// we need to block it initially to compensate for this.
g_signal_handler_block(widget, m_handlerAfterKeyPress);
}
void StartHandlingKeyPress(GtkWidget* widget)
{
m_inKeyPress = true;
m_pendingTextChanged = false;
g_signal_handler_unblock(widget, m_handlerAfterKeyPress);
}
bool SetPendingIfInKeyPress()
{
if ( !m_inKeyPress )
return false;
m_pendingTextChanged = true;
return true;
}
bool EndHandlingKeyPressAndCheckIfPending(GtkWidget* widget)
{
g_signal_handler_block(widget, m_handlerAfterKeyPress);
wxASSERT( m_inKeyPress );
m_inKeyPress = false;
if ( !m_pendingTextChanged )
return false;
m_pendingTextChanged = false;
return true;
}
private:
bool m_inKeyPress;
bool m_pendingTextChanged;
const gulong m_handlerAfterKeyPress;
wxDECLARE_NO_COPY_CLASS(wxTextCoalesceData);
};
//-----------------------------------------------------------------------------
// helper function to get the length of the text
//-----------------------------------------------------------------------------
static int GetEntryTextLength(GtkEntry* entry)
{
#if GTK_CHECK_VERSION(2, 14, 0)
if ( wx_is_at_least_gtk2(14) )
{
return gtk_entry_get_text_length(entry);
}
#endif // GTK+ 2.14+
return strlen(gtk_entry_get_text(entry));
}
// ============================================================================
// signal handlers implementation
// ============================================================================
extern "C" {
// "event-after" handler is only connected when we get a "key-press-event", so
// it's effectively called after the end of processing of this event and used
// to send a single wxEVT_TEXT even if we received several (typically two, when
// the selected text in the control is replaced by new text) "changed" signals.
static gboolean
wx_gtk_text_after_key_press(GtkWidget* widget,
GdkEventKey* WXUNUSED(gdk_event),
wxTextEntry* entry)
{
wxTextCoalesceData* const data = entry->GTKGetCoalesceData();
wxCHECK_MSG( data, FALSE, "must be non-null if this handler is called" );
if ( data->EndHandlingKeyPressAndCheckIfPending(widget) )
{
entry->GTKOnTextChanged();
}
return FALSE;
}
// "changed" handler for GtkEntry
static void
wx_gtk_text_changed_callback(GtkWidget* WXUNUSED(widget), wxTextEntry* entry)
{
if ( wxTextCoalesceData* const data = entry->GTKGetCoalesceData() )
{
if ( data->SetPendingIfInKeyPress() )
{
// Don't send the event right now as more might be coming.
return;
}
}
entry->GTKOnTextChanged();
}
// "insert_text" handler for GtkEntry
static void
wx_gtk_insert_text_callback(GtkEditable *editable,
const gchar * new_text,
gint new_text_length,
gint * position,
wxTextEntry *text)
{
GtkEntry *entry = GTK_ENTRY (editable);
#if GTK_CHECK_VERSION(3,0,0) || defined(GSEAL_ENABLE)
const int text_max_length = gtk_entry_buffer_get_max_length(gtk_entry_get_buffer(entry));
#else
const int text_max_length = entry->text_max_length;
#endif
bool handled = false;
// check that we don't overflow the max length limit if we have it
if ( text_max_length )
{
const int text_length = GetEntryTextLength(entry);
// We can't use new_text_length as it is in bytes while we want to count
// characters (in first approximation, anyhow...).
if ( text_length + g_utf8_strlen(new_text, -1) > text_max_length )
{
// Prevent the new text from being inserted.
handled = true;
// Currently we don't insert anything at all, but it would be better to
// insert as many characters as would fit into the text control and
// only discard the rest.
// Notify the user code about overflow.
text->SendMaxLenEvent();
}
}
// Check if we have to convert all input to upper-case
if ( !handled && text->GTKIsUpperCase() )
{
const wxGtkString upper(g_utf8_strup(new_text, new_text_length));
// Use the converted text to generate events
if ( !text->GTKEntryOnInsertText(upper) )
{
// Event not handled, so do insert the text: we have to do it
// ourselves to use the upper-case version of it
// Prevent recursive call to this handler again
g_signal_handlers_block_by_func
(
editable,
(gpointer)wx_gtk_insert_text_callback,
text
);
gtk_editable_insert_text(editable, upper, strlen(upper), position);
g_signal_handlers_unblock_by_func
(
editable,
(gpointer)wx_gtk_insert_text_callback,
text
);
}
// Don't call the default handler in any case, either the event was
// handled in the user code or we've already inserted the text.
handled = true;
}
if ( !handled && text->GTKEntryOnInsertText(new_text) )
{
// If we already handled the new text insertion, don't do it again.
handled = true;
}
if ( handled )
{
// We must update the position to point after the newly inserted text,
// as expected by GTK+.
*position = text->GetInsertionPoint();
g_signal_stop_emission_by_name (editable, "insert_text");
}
}
// GTK+ does not expose any mechanism that we can really rely on to detect if/when
// the completion popup is shown or hidden. And the sole reliable way (for now) to
// know its state is to connect to the "grab-notify" signal and be notified then
// for its state. this is the best we can do for now than any other alternative.
// (GtkEntryCompletion grabs/ungrabs keyboard and mouse events on popups/popdowns).
static void
wx_gtk_entry_parent_grab_notify (GtkWidget *widget,
gboolean was_grabbed,
wxTextAutoCompleteData *data);
} // extern "C"
//-----------------------------------------------------------------------------
// clipboard events: "copy-clipboard", "cut-clipboard", "paste-clipboard"
//-----------------------------------------------------------------------------
// common part of the event handlers below
static void
DoHandleClipboardCallback( GtkWidget *widget,
wxWindow *win,
wxEventType eventType,
const gchar* signal_name)
{
wxClipboardTextEvent event( eventType, win->GetId() );
event.SetEventObject( win );
if ( win->HandleWindowEvent( event ) )
{
// don't let the default processing to take place if we did something
// ourselves in the event handler
g_signal_stop_emission_by_name (widget, signal_name);
}
}
extern "C"
{
static void
wx_gtk_copy_clipboard_callback( GtkWidget *widget, wxWindow *win )
{
DoHandleClipboardCallback(
widget, win, wxEVT_TEXT_COPY, "copy-clipboard" );
}
static void
wx_gtk_cut_clipboard_callback( GtkWidget *widget, wxWindow *win )
{
DoHandleClipboardCallback(
widget, win, wxEVT_TEXT_CUT, "cut-clipboard" );
}
static void
wx_gtk_paste_clipboard_callback( GtkWidget *widget, wxWindow *win )
{
DoHandleClipboardCallback(
widget, win, wxEVT_TEXT_PASTE, "paste-clipboard" );
}
} // extern "C"
// Base class for wxTextAutoCompleteFixed and wxTextAutoCompleteDynamic below.
class wxTextAutoCompleteData
{
public:
// This method is only implemented by wxTextAutoCompleteFixed and will just
// return false for wxTextAutoCompleteDynamic.
virtual bool ChangeStrings(const wxArrayString& strings) = 0;
// Conversely, this one is only implemented for wxTextAutoCompleteDynamic
// and will just return false (without taking ownership of the argument!)
// for wxTextAutoCompleteFixed.
virtual bool ChangeCompleter(wxTextCompleter* completer) = 0;
// We should toggle off wxTE_PROCESS_ENTER flag of our wxTextEntry while
// the completion popup is shown to let it see Enter event and process it
// on its own (e.g. to dismiss itself). This is done by "grab-notify" signal
// see wxTextCtrl::OnChar()
void ToggleProcessEnterFlag(bool toggleOff)
{
wxWindow* const win = GetEditableWindow(m_entry);
long flags = win->GetWindowStyleFlag();
if ( toggleOff )
{
// Store the original window flags before we change them.
m_hadProcessEnterFlag = (flags & wxTE_PROCESS_ENTER) != 0;
if ( !m_hadProcessEnterFlag )
{
// No need to do anything, it was already off.
return;
}
flags &= ~wxTE_PROCESS_ENTER;
}
else // Restore the original flags.
{
if ( !m_hadProcessEnterFlag )
{
// We hadn't turned it off, no need to turn it back on.
return;
}
flags |= wxTE_PROCESS_ENTER;
}
win->SetWindowStyleFlag(flags);
}
virtual ~wxTextAutoCompleteData()
{
// Note that we must not use m_entry here because this could result in
// using an already half-destroyed wxTextEntry when we're destroyed
// from its dtor (which is executed after wxTextCtrl dtor, which had
// already destroyed the actual entry). So use the stored widget
// instead and only after checking that it is still valid.
if ( GTK_IS_ENTRY(m_widgetEntry) )
{
gtk_entry_set_completion(m_widgetEntry, NULL);
g_signal_handlers_disconnect_by_data(m_widgetEntry, this);
}
}
protected:
// Check if completion can be used with this entry.
static bool CanComplete(wxTextEntry* entry)
{
// If this check fails, this is probably a multiline wxTextCtrl which
// doesn't have any associated GtkEntry.
return GTK_IS_ENTRY(entry->GetEntry());
}
explicit wxTextAutoCompleteData(wxTextEntry* entry)
: m_entry(entry),
m_widgetEntry(entry->GetEntry())
{
// This will be really set in ToggleProcessEnterFlag().
m_hadProcessEnterFlag = false;
GtkEntryCompletion* const completion = gtk_entry_completion_new();
gtk_entry_completion_set_text_column (completion, 0);
gtk_entry_set_completion(m_widgetEntry, completion);
g_signal_connect (m_widgetEntry, "grab-notify",
G_CALLBACK (wx_gtk_entry_parent_grab_notify),
this);
}
// Provide access to wxTextEntry::GetEditableWindow() to the derived
// classes: we can call it because this class is a friend of wxTextEntry,
// but the derived classes can't do it directly.
static wxWindow* GetEditableWindow(wxTextEntry* entry)
{
return entry->GetEditableWindow();
}
// Helper function for appending a string to GtkListStore.
void AppendToStore(GtkListStore* store, const wxString& s)
{
GtkTreeIter iter;
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, 0, (const gchar *)s.utf8_str(), -1);
}
// Really change the completion model (which may be NULL).
void UseModel(GtkListStore* store)
{
GtkEntryCompletion* const c = gtk_entry_get_completion(m_widgetEntry);
gtk_entry_completion_set_model (c, GTK_TREE_MODEL(store));
gtk_entry_completion_complete (c);
}
// The text entry we're associated with.
wxTextEntry * const m_entry;
// And its GTK widget.
GtkEntry* const m_widgetEntry;
// True if the window had wxTE_PROCESS_ENTER flag before we turned it off
// in ToggleProcessEnterFlag().
bool m_hadProcessEnterFlag;
wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteData);
};
// This class simply forwards to GtkListStore.
class wxTextAutoCompleteFixed : public wxTextAutoCompleteData
{
public:
// Factory function, may return NULL if entry is invalid.
static wxTextAutoCompleteFixed* New(wxTextEntry *entry)
{
if ( !CanComplete(entry) )
return NULL;
return new wxTextAutoCompleteFixed(entry);
}
virtual bool ChangeStrings(const wxArrayString& strings) wxOVERRIDE
{
wxGtkObject<GtkListStore> store(gtk_list_store_new (1, G_TYPE_STRING));
for ( wxArrayString::const_iterator i = strings.begin();
i != strings.end();
++i )
{
AppendToStore(store, *i);
}
UseModel(store);
return true;
}
virtual bool ChangeCompleter(wxTextCompleter*) wxOVERRIDE
{
return false;
}
private:
// Ctor is private, use New() to create objects of this type.
explicit wxTextAutoCompleteFixed(wxTextEntry *entry)
: wxTextAutoCompleteData(entry)
{
}
wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteFixed);
};
// Dynamic completion using the provided custom wxTextCompleter.
class wxTextAutoCompleteDynamic : public wxTextAutoCompleteData
{
public:
static wxTextAutoCompleteDynamic* New(wxTextEntry *entry)
{
if ( !CanComplete(entry) )
return NULL;
wxWindow * const win = GetEditableWindow(entry);
if ( !win )
return NULL;
return new wxTextAutoCompleteDynamic(entry, win);
}
virtual ~wxTextAutoCompleteDynamic()
{
delete m_completer;
m_win->Unbind(wxEVT_TEXT, &wxTextAutoCompleteDynamic::OnEntryChanged, this);
}
virtual bool ChangeStrings(const wxArrayString&) wxOVERRIDE
{
return false;
}
// Takes ownership of the pointer which must be non-NULL.
virtual bool ChangeCompleter(wxTextCompleter *completer) wxOVERRIDE
{
delete m_completer;
m_completer = completer;
DoUpdateCompletionModel();
return true;
}
private:
// Ctor is private, use New() to create objects of this type.
explicit wxTextAutoCompleteDynamic(wxTextEntry *entry, wxWindow *win)
: wxTextAutoCompleteData(entry),
m_win(win)
{
m_completer = NULL;
win->Bind(wxEVT_TEXT, &wxTextAutoCompleteDynamic::OnEntryChanged, this);
}
void OnEntryChanged(wxCommandEvent& event)
{
DoUpdateCompletionModel();
event.Skip();
}
// Recreate the model to contain all completions for the current prefix.
void DoUpdateCompletionModel()
{
const wxString& prefix = m_entry->GetValue();
if ( m_completer->Start(prefix) )
{
wxGtkObject<GtkListStore> store(gtk_list_store_new (1, G_TYPE_STRING));
for (;;)
{
const wxString s = m_completer->GetNext();
if ( s.empty() )
break;
AppendToStore(store, s);
}
UseModel(store);
}
else
{
UseModel(NULL);
}
}
// Custom completer.
wxTextCompleter *m_completer;
// The associated window, we need to store it to unbind our event handler.
wxWindow* const m_win;
wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteDynamic);
};
extern "C"
{
static void
wx_gtk_entry_parent_grab_notify (GtkWidget *widget,
gboolean was_grabbed,
wxTextAutoCompleteData *data)
{
g_return_if_fail (GTK_IS_ENTRY(widget));
bool toggleOff = false;
if ( gtk_widget_has_focus(widget) )
{
// If was_grabbed is FALSE that means the topmost grab widget ancestor
// of our GtkEntry becomes shadowed by a call to gtk_grab_add()
// which means that the GtkEntryCompletion popup window is actually
// shown on screen.
if ( !was_grabbed )
toggleOff = true;
}
data->ToggleProcessEnterFlag(toggleOff);
}
} // extern "C"
// ============================================================================
// wxTextEntry implementation
// ============================================================================
// ----------------------------------------------------------------------------
// initialization and destruction
// ----------------------------------------------------------------------------
wxTextEntry::wxTextEntry()
{
m_autoCompleteData = NULL;
m_coalesceData = NULL;
m_isUpperCase = false;
}
wxTextEntry::~wxTextEntry()
{
delete m_coalesceData;
delete m_autoCompleteData;
}
// ----------------------------------------------------------------------------
// text operations
// ----------------------------------------------------------------------------
void wxTextEntry::WriteText(const wxString& value)
{
GtkEditable * const edit = GetEditable();
// remove the selection if there is one and suppress the text change event
// generated by this: we only want to generate one event for this change,
// not two
{
EventsSuppressor noevents(this);
gtk_editable_delete_selection(edit);
}
// insert new text at the cursor position
gint len = gtk_editable_get_position(edit);
gtk_editable_insert_text
(
edit,
wxGTK_CONV_FONT(value, GetEditableWindow()->GetFont()),
-1, // text: length: compute it using strlen()
&len // will be updated to position after the text end
);
// and move cursor to the end of new text
gtk_editable_set_position(edit, len);
}
void wxTextEntry::DoSetValue(const wxString& value, int flags)
{
if (value != DoGetValue())
{
// Use Remove() rather than SelectAll() to avoid unnecessary clipboard
// operations, and prevent triggering an apparent bug in GTK which
// causes the subsequent WriteText() to append rather than overwrite.
{
EventsSuppressor noevents(this);
Remove(0, -1);
}
// Testing whether value is empty here is more than just an
// optimization: WriteText() always generates an explicit event in
// wxGTK, which we need to avoid unless SetValue_SendEvent is given.
if ( !value.empty() )
{
// Suppress events from here even if we do need them, it's simpler
// to send the event below in all cases.
EventsSuppressor noevents(this);
WriteText(value);
}
// Changing the value is supposed to reset the insertion point. Note,
// however, that this does not happen if the text doesn't really change.
SetInsertionPoint(0);
}
// OTOH we must send the event even if the text didn't really change for
// consistency.
if ( flags & SetValue_SendEvent )
SendTextUpdatedEvent(GetEditableWindow());
}
wxString wxTextEntry::DoGetValue() const
{
const wxGtkString value(gtk_editable_get_chars(GetEditable(), 0, -1));
return wxGTK_CONV_BACK_FONT(value,
const_cast<wxTextEntry *>(this)->GetEditableWindow()->GetFont());
}
void wxTextEntry::Remove(long from, long to)
{
gtk_editable_delete_text(GetEditable(), from, to);
}
// static
int wxTextEntry::GTKGetEntryTextLength(GtkEntry* entry)
{
return GetEntryTextLength(entry);
}
// ----------------------------------------------------------------------------
// clipboard operations
// ----------------------------------------------------------------------------
void wxTextEntry::GTKConnectClipboardSignals(GtkWidget* entry)
{
g_signal_connect(entry, "copy-clipboard",
G_CALLBACK (wx_gtk_copy_clipboard_callback),
GetEditableWindow());
g_signal_connect(entry, "cut-clipboard",
G_CALLBACK (wx_gtk_cut_clipboard_callback),
GetEditableWindow());
g_signal_connect(entry, "paste-clipboard",
G_CALLBACK (wx_gtk_paste_clipboard_callback),
GetEditableWindow());
}
void wxTextEntry::Copy()
{
gtk_editable_copy_clipboard(GetEditable());
}
void wxTextEntry::Cut()
{
gtk_editable_cut_clipboard(GetEditable());
}
void wxTextEntry::Paste()
{
gtk_editable_paste_clipboard(GetEditable());
}
// ----------------------------------------------------------------------------
// undo/redo
// ----------------------------------------------------------------------------
void wxTextEntry::Undo()
{
// TODO: not implemented
}
void wxTextEntry::Redo()
{
// TODO: not implemented
}
bool wxTextEntry::CanUndo() const
{
return false;
}
bool wxTextEntry::CanRedo() const
{
return false;
}
// ----------------------------------------------------------------------------
// insertion point
// ----------------------------------------------------------------------------
void wxTextEntry::SetInsertionPoint(long pos)
{
gtk_editable_set_position(GetEditable(), pos);
}
long wxTextEntry::GetInsertionPoint() const
{
return gtk_editable_get_position(GetEditable());
}
long wxTextEntry::GetLastPosition() const
{
// this can't be implemented for arbitrary GtkEditable so only do it for
// GtkEntries
long pos = -1;
GtkEntry* entry = (GtkEntry*)GetEditable();
if (GTK_IS_ENTRY(entry))
pos = GetEntryTextLength(entry);
return pos;
}
// ----------------------------------------------------------------------------
// selection
// ----------------------------------------------------------------------------
void wxTextEntry::SetSelection(long from, long to)
{
// in wx convention, (-1, -1) means the entire range but GTK+ translates -1
// (or any negative number for that matter) into last position so we need
// to translate manually
if ( from == -1 && to == -1 )
from = 0;
// for compatibility with MSW, exchange from and to parameters so that the
// insertion point is set to the start of the selection and not its end as
// GTK+ does by default
gtk_editable_select_region(GetEditable(), to, from);
#ifndef __WXGTK3__
// avoid reported problem with RHEL 5 GTK+ 2.10 where selection is reset by
// a clipboard callback, see #13277
if (!wx_is_at_least_gtk2(12))
{
GtkEntry* entry = GTK_ENTRY(GetEditable());
if (to < 0)
to = entry->text_length;
entry->selection_bound = to;
}
#endif
}
void wxTextEntry::GetSelection(long *from, long *to) const
{
gint start, end;
if ( gtk_editable_get_selection_bounds(GetEditable(), &start, &end) )
{
// the output must always be in order, although in GTK+ it isn't
if ( start > end )
{
gint tmp = start;
start = end;
end = tmp;
}
}
else // no selection
{
// for compatibility with MSW return the empty selection at cursor
start =
end = GetInsertionPoint();
}
if ( from )
*from = start;
if ( to )
*to = end;
}
// ----------------------------------------------------------------------------
// auto completion
// ----------------------------------------------------------------------------
bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices)
{
// Try to update the existing data first.
if ( !m_autoCompleteData || !m_autoCompleteData->ChangeStrings(choices) )
{
delete m_autoCompleteData;
m_autoCompleteData = NULL;
// If it failed, try creating a new object for fixed completion.
wxTextAutoCompleteFixed* const ac = wxTextAutoCompleteFixed::New(this);
if ( !ac )
return false;
ac->ChangeStrings(choices);
m_autoCompleteData = ac;
}
return true;
}
bool wxTextEntry::DoAutoCompleteCustom(wxTextCompleter *completer)
{
// First deal with the case when we just want to disable auto-completion.
if ( !completer )
{
if ( m_autoCompleteData )
{
delete m_autoCompleteData;
m_autoCompleteData = NULL;
}
//else: Nothing to do, we hadn't used auto-completion even before.
}
else // Have a valid completer.
{
// As above, try to update the completer of the existing object first
// and fall back on creating a new one.
if ( !m_autoCompleteData ||
!m_autoCompleteData->ChangeCompleter(completer) )
{
delete m_autoCompleteData;
m_autoCompleteData = NULL;
wxTextAutoCompleteDynamic* const
ac = wxTextAutoCompleteDynamic::New(this);
if ( !ac )
return false;
ac->ChangeCompleter(completer);
m_autoCompleteData = ac;
}
}
return true;
}
// ----------------------------------------------------------------------------
// editable status
// ----------------------------------------------------------------------------
bool wxTextEntry::IsEditable() const
{
return gtk_editable_get_editable(GetEditable()) != 0;
}
void wxTextEntry::SetEditable(bool editable)
{
gtk_editable_set_editable(GetEditable(), editable);
}
// ----------------------------------------------------------------------------
// input restrictions
// ----------------------------------------------------------------------------
void wxTextEntry::SetMaxLength(unsigned long len)
{
GtkEntry* const entry = (GtkEntry*)GetEditable();
if (!GTK_IS_ENTRY(entry))
return;
gtk_entry_set_max_length(entry, len);
}
void wxTextEntry::SendMaxLenEvent()
{
// remember that the next changed signal is to be ignored to avoid
// generating a dummy wxEVT_TEXT event
//IgnoreNextTextUpdate();
wxWindow * const win = GetEditableWindow();
wxCommandEvent event(wxEVT_TEXT_MAXLEN, win->GetId());
event.SetEventObject(win);
event.SetString(GetValue());
win->HandleWindowEvent(event);
}
void wxTextEntry::ForceUpper()
{
if ( !m_isUpperCase )
{
ConvertToUpperCase();
m_isUpperCase = true;
}
}
// ----------------------------------------------------------------------------
// IM handling
// ----------------------------------------------------------------------------
void wxTextEntry::GTKEntryOnKeypress(GtkWidget* widget) const
{
// We coalesce possibly multiple events resulting from a single key press
// (this always happens when there is a selection, as we always get a
// "changed" event when the selection is removed and another one when the
// new text is inserted) into a single wxEVT_TEXT and to do this we need
// this extra handler.
if ( !m_coalesceData )
{
// We can't use g_signal_connect_after("key-press-event") because the
// emission of this signal is stopped by GtkEntry own key-press-event
// handler, so we have to use the generic "event-after" instead to be
// notified about the end of handling of this key press and to send any
// pending events a.s.a.p.
const gulong handler = g_signal_connect
(
widget,
"event-after",
G_CALLBACK(wx_gtk_text_after_key_press),
const_cast<wxTextEntry*>(this)
);
m_coalesceData = new wxTextCoalesceData(widget, handler);
}
m_coalesceData->StartHandlingKeyPress(widget);
}
int wxTextEntry::GTKEntryIMFilterKeypress(GdkEventKey* event) const
{
GTKEntryOnKeypress(GTK_WIDGET(GetEntry()));
int result = false;
#if GTK_CHECK_VERSION(2, 22, 0)
if (wx_is_at_least_gtk2(22))
{
result = gtk_entry_im_context_filter_keypress(GetEntry(), event);
}
#else // GTK+ < 2.22
wxUnusedVar(event);
#endif // GTK+ 2.22+
return result;
}
// ----------------------------------------------------------------------------
// signals and events
// ----------------------------------------------------------------------------
void wxTextEntry::EnableTextChangedEvents(bool enable)
{
// Check that we have the associated text, as it may happen (for e.g.
// read-only wxBitmapComboBox) and shouldn't result in any errors, we just
// don't have any events to enable or disable in this case.
void* const entry = GetTextObject();
if ( !entry )
return;
if ( enable )
{
g_signal_handlers_unblock_by_func(entry,
(gpointer)wx_gtk_text_changed_callback, this);
}
else // disable events
{
g_signal_handlers_block_by_func(entry,
(gpointer)wx_gtk_text_changed_callback, this);
}
}
void wxTextEntry::GTKConnectChangedSignal()
{
g_signal_connect(GetTextObject(), "changed",
G_CALLBACK(wx_gtk_text_changed_callback), this);
}
void wxTextEntry::GTKConnectInsertTextSignal(GtkEntry* entry)
{
g_signal_connect(entry, "insert_text",
G_CALLBACK(wx_gtk_insert_text_callback), this);
}
bool wxTextEntry::GTKEntryOnInsertText(const char* text)
{
return GetEditableWindow()->GTKOnInsertText(text);
}
// ----------------------------------------------------------------------------
// margins support
// ----------------------------------------------------------------------------
bool wxTextEntry::DoSetMargins(const wxPoint& margins)
{
#if GTK_CHECK_VERSION(2,10,0) && !defined(__WXGTK4__)
GtkEntry* entry = GetEntry();
if ( !entry )
return false;
if ( !wx_is_at_least_gtk2(10) )
return false;
wxGCC_WARNING_SUPPRESS(deprecated-declarations)
const GtkBorder* oldBorder = gtk_entry_get_inner_border(entry);
GtkBorder newBorder;
if ( oldBorder )
newBorder = *oldBorder;
else
{
// Use some reasonable defaults for initial margins
newBorder.left = 2;
newBorder.right = 2;
// These numbers seem to let the text remain vertically centered
// in common use scenarios when margins.y == -1.
newBorder.top = 3;
newBorder.bottom = 3;
}
if ( margins.x != -1 )
newBorder.left = margins.x;
if ( margins.y != -1 )
newBorder.top = margins.y;
gtk_entry_set_inner_border(entry, &newBorder);
wxGCC_WARNING_RESTORE()
return true;
#else
wxUnusedVar(margins);
return false;
#endif
}
wxPoint wxTextEntry::DoGetMargins() const
{
wxPoint point(-1, -1);
#if GTK_CHECK_VERSION(2,10,0) && !defined(__WXGTK4__)
GtkEntry* entry = GetEntry();
if (entry)
{
if (wx_is_at_least_gtk2(10))
{
wxGCC_WARNING_SUPPRESS(deprecated-declarations)
const GtkBorder* border = gtk_entry_get_inner_border(entry);
wxGCC_WARNING_RESTORE()
if (border)
{
point.x = border->left;
point.y = border->top;
}
}
}
#endif
return point;
}
#ifdef __WXGTK3__
bool wxTextEntry::SetHint(const wxString& hint)
{
#if GTK_CHECK_VERSION(3,2,0)
GtkEntry *entry = GetEntry();
if (entry && gtk_check_version(3,2,0) == NULL)
{
gtk_entry_set_placeholder_text
(
entry,
wxGTK_CONV_FONT(hint, GetEditableWindow()->GetFont())
);
return true;
}
#endif
return wxTextEntryBase::SetHint(hint);
}
wxString wxTextEntry::GetHint() const
{
#if GTK_CHECK_VERSION(3,2,0)
GtkEntry *entry = GetEntry();
if (entry && gtk_check_version(3,2,0) == NULL)
{
return wxGTK_CONV_BACK_FONT
(
gtk_entry_get_placeholder_text(entry),
const_cast<wxTextEntry *>(this)->GetEditableWindow()->GetFont()
);
}
#endif
return wxTextEntryBase::GetHint();
}
#endif // __WXGTK3__
bool wxTextEntry::ClickDefaultButtonIfPossible()
{
GtkWidget* const widget = GTK_WIDGET(GetEntry());
// This does the same thing as gtk_entry_real_activate() in GTK itself.
//
// Note: in GTK 4 we should probably just use gtk_widget_activate_default().
GtkWidget* const toplevel = gtk_widget_get_toplevel(widget);
if ( GTK_IS_WINDOW (toplevel) )
{
GtkWindow* const window = GTK_WINDOW(toplevel);
if ( window )
{
GtkWidget* const default_widget = gtk_window_get_default_widget(window);
GtkWidget* const focus_widget = gtk_window_get_focus(window);
if ( widget != default_widget &&
!(widget == focus_widget &&
(!default_widget ||
!gtk_widget_get_sensitive(default_widget))) )
{
if ( gtk_window_activate_default(window) )
return true;
}
}
}
return false;
}
#endif // wxUSE_TEXTCTRL || wxUSE_COMBOBOX