c++gtkmmgtkmm4

How to move the Gtk::Entry cursor?


I'm trying to make a custom Gtk::Entry widget (gtkmm4) that accepts only numbers and shows text as currency. Decimal and thousand separators are automatically added to the text. So I derived from Gtk::Entry and connected the signal_changed() with a member function that formats the input:

class CurrencyEntry : public Gtk::Entry{

    public:

    CurrencyEntry() {

        set_placeholder_text("0.00");
        connectionChange = signal_changed().connect(
            sigc::mem_fun(*this, &CurrencyEntry::filterInput)
        );      
    }

    protected:

    sigc::connection connectionChange;
    Glib::ustring oldText;

    void filterInput(){

        auto currentText = get_text();

        /* format currentText */

        connectionChange.block();
        set_text(currentText);
        connectionChange.unblock();

        /* move the cursor */
    }

};

The problem is: the user presses one key at time, but more than one symbol can be added to the text in specific cases. It seems that the default behavior of the cursor is to always move 1 place per key pressed, ignoring the extra symbols. This is the result (| is the cursor):

Current Text Typed Key Result Desired Result
| (empty) 1 0|.01 0.01|
123.45| 6 1,234.5|6 1,234.56|
98|0,123.45| 7 9,8|70,123.45 9,87|0,123.45

I need to move the cursor to the correct position. At first seemed trivial, but so far I have tried:

Nothing happens. All these commands seem to be ignored or ignore the position parameter. Am I doing something wrong or is this some kind of bug? How can I set the cursor position correctly in a Gtk::Entry widget?


Solution

  • The position seems not to be updated from the entry's handler. I tried other handlers (like insert_text) and the same issue arises. One way to solve this is to, from within you entry's handler, add a function to be executed in the idle loop. In that function, you can update the position. Here is the code:

    #include <algorithm>
    #include <iostream>
    #include <string>
    
    #include <gtkmm.h>
    
    class CurrencyEntry : public Gtk::Entry
    {
    
    public:
    
        CurrencyEntry()
        {
            m_connection = signal_changed().connect(
            [this]()
            {
                // Get the current edit box content:
                std::string str = get_text();
    
                    // Make it upper case:
                    std::transform(str.begin(), str.end(), str.begin(), ::toupper);
    
                    // Set the updated text. The connection is blocked to avoid
                    // recursion:
                    m_connection.block();
                    set_text(str);
                    m_connection.unblock();
    
                    // Update the position in the idle loop:
                    Glib::signal_idle().connect(
                    [this]()
                    {
                        set_position(2);
                        return false;
                    });
            });
        }
    
    private:
    
        sigc::connection m_connection;
    };
    
    class MainWindow : public Gtk::ApplicationWindow
    {
    
    public:
    
        MainWindow();
    
    private:
    
        CurrencyEntry m_entry;
    
    };
    
    MainWindow::MainWindow()
    {
        add(m_entry);
    }
    
    int main(int argc, char *argv[])
    {
        auto app = Gtk::Application::create(argc, argv, "org.gtkmm.examples.base");
      
        MainWindow window;
        window.show_all();
      
        return app->run(window);
    }
    

    This is a simplified version of you case: all inserted text is transformed to upper case and, if possible, the cursor's position is set to 2. I think you can adapt to your use case from that.