Handle DatePicker with Ruby / gtk3 + glade3

Preamble

If gtk doesn't have a DatePicker and it's hard to use ... If you're wondering, @ kojix2 can do it with gtk3 in combination with PopOver! Advised me.

I want to cover the GUI of the front end with ruby, and I want to write the GUI part with the builder tool, but now that wxWidget can no longer be used, I was complaining that I had to choose between QT creator and JavaFX Scene Editor. I got a reply like this. Since you showed me the code of gtk3, I tried to make a text box (Gtk :: Entry) that can select a date such as DatePicker or DateChooser with glade. Or rather, a memo to write before forgetting.

Preparation

The operating premise is ruby2.7, gtk3, glade 3.36.0. I think that ruby can be introduced easily, but there was a slight trap in glade on Windows, so please refer to the following.

macOS and UnixLikeOS

For Debian, apt + gem can be used, and for macOS, homebrew can be used.

Windows

When I try to use glade on Windows, when I look at the official site, I find that the executable binaries are only old ones such as for gtk2. So I decided to use msys2 with scoop. (For the introduction of scoop, you can refer to Another Qiita article)

After installing msys2, install gtk library and glade3 with pacman on bash of msys2. Please note that you do not install glade directly with scoop.

$ pacman -S mingw-w64-x86_64-gtk3 mingw-w64-x86_64-glade

$ /mingw64/bin/glade

If you can start glade as above, you are ready (please prepare ruby as well)

Make a river with glade

I will make a river according to the style of gtk (?). If you compare the menu on the glade screen to the web, HTML / BODY is the top level (≒ window), buttons and text fields with user operations are Control (Button, Entry, etc.), display labels and images are Display, div. Is it a container (GtkBox, etc.)?

This time, when the text field (GtkEntry) is focused, I want to display the widget on the calendar, so in addition to the main window, create a container to popover.

image.png

Since I have to make it myself, I will add a button to select yesterday to tomorrow in addition to the calendar. I am changing.

I will give each widget a name by ID so that I can call it from ruby later. Also, register a method that executes an event such as a button click by selecting it from the Signal tab as a handler. Please note that an error will occur if there is no method registered as a handler when executing ruby.

The .glade file that is saved is XML.

The disgusting part

Write a handler in the signal focus-in-event event of GtkEntry that calls DatePicker so that PopOver is displayed when the focus comes. However, on the contrary, if you close PopOver when the focus is lost, it is natural that Calendar cannot be operated, so be careful.

Assign a handler to the day-select-double-click signal when double-clicking a date in Calender

Create a "Close" button as a condition to turn off the PopOver display, and set visible = false in the clicked signal.

Operation trivia

With GtkWindow selected in the left pane, press the gear button at the bottom to preview at runtime.

In the old Visual BASIC and Delphi, it was standard that buttons could be arranged freely under the window, but in gtk, it seems that it is basically packed in a container etc., added, and automatically laid out. For free placement, use a container called GtkFixed. Widgets placed inside Fixed can be moved by holding down the Shift key while dragging.

Glade, who is a GUI builder but can't keep moving widgets, makes adjustments by moving the order of positions on the packing tab. If you want to move it to another Box, Cut and Paste ...

Create implementation code

There is a part I tried with mvc, so for a simple code, refer to Original tweet ...

app.rb


# frozen_string_literal: true

%w[pp gtk3 date observer].each { |lib| require lib }

class ObjectController
  include Observable
  attr_reader :content

  def content=(object)
    @content = object
    @content.add_observer(self)
    @content
  end

  def update
    changed
    notify_observers
    self
  end
end

class View
  attr_reader :widget

  def controller=(c)
    @controller = c
    @controller.add_observer(self)
    c
  end
end

class State
  include Observable
  def initialize
    @select_date = Date.today
  end

  attr_reader :selected_date

  def selected_date=(date)
    @selected_date = date
    changed
    notify_observers
    date
  end
end

class SelectedDateView < View
  def initialize(parent_builder)
    @widget = parent_builder.get_object('entrydate')
    @widget.signal_connect('changed') do
      @controller.content.selected_date = value
      true
    end
  end

  def value
    @widget.text
  end

  def value=(f)
    @widget.text = f.to_s
  end

  def update
    self.value = @controller.content.selected_date
    self
  end
end

class GyoumuApp
  def initialize
    @select_date = Date.today

    @builder = Gtk::Builder.new(file: 'glade1.glade')

    @seldate_controller = ObjectController.new
    @seldate_controller.content = State.new
    @seldate_view = SelectedDateView.new(@builder)
    @seldate_view.controller = @seldate_controller

    @win = @builder.get_object('main')
    @date_picker = @builder.get_object('date_picker') # PopOver
    @calendar = @builder.get_object('calendar') #Calendar in PopOver

    @buf_code = @builder.get_object('entrybuffer1')
    @buf_code.text = 'K6205'

    @btn_yday = @builder.get_object('select_yesterday')
    @btn_tday = @builder.get_object('select_today')
    @btn_tmrw = @builder.get_object('select_tomorrow')

    #Button in PopOver sets signal handler from ruby
    @btn_yday.signal_connect('clicked') do
      select_day(-1)
    end
    @btn_tday.signal_connect('clicked') do
      select_day(0)
    end
    @btn_tmrw.signal_connect('clicked') do
      select_day(1)
    end

    @builder.connect_signals { |handler| method(handler) } #handler is String
  end

  def select_day(day)
    @seldate_controller.content.selected_date = Date.today + day
    click_dateclose
  end

  # [✕]Exit the app when is pressed
  def on_main_destroy
    Gtk.main_quit
  end

  #When the focus shifts to the entry date
  def focus_in_entrydate
    @date_picker.visible = true
  end

  #When you double-click a date on the calendar
  def dblclick_date
    @seldate_controller.content.selected_date = sprintf('%04d-%02d-%02d', @calendar.year, @calendar.month, @calendar.day)
    click_dateclose
  end

  # date_Press the cancel button with picker
  def click_dateclose
    @date_picker.visible = false
  end
end

class App < GyoumuApp
  def initialize
    super
    @win.show_all
    Gtk.main
  end
end
App.new

Summary

Click here for miscellaneous repositories (https://github.com/paichi81/glade_tut1)

The gtk3 library is useful because you can read the glade file with Gtk :: Builder.new and use it quickly.

It is convenient to use ready-made parts such as WxWidget's DatePicker quickly, but when you want to add your own button like this time, you can not turn around. It turns out that gtk can be solved with a unix-like approach. It's just a hassle ...

Looking at the glade settings, the parts that can be tampered with with gtk, which is difficult to stick to, are quite detailed. To put it the other way around, raw gtk has to be very conscious of widgets, so I miss something like MVVM.

Recommended Posts

Handle DatePicker with Ruby / gtk3 + glade3
[Ruby] Handle instance variables with instance methods
Install Ruby 3.0.0 with asdf
Handle devise with Rails
Handle JSON with minimal-json
Getting Started with Ruby
11th, Classification with Ruby
Evolve Eevee with Ruby
Solve Google problems with Ruby
I tried DI with Ruby
GraphQL Client starting with Ruby
Ruby: Send email with Starttls
Leap year judgment with Ruby
Format Ruby with VS Code
Integer check method with ruby
Ruby Learning # 8 Working With String
[Ruby] problem with if statement
Studying with CodeWar (ruby) ⑤ proc
Use Ruby with Google Colab
[Ruby] REPL-driven development with pry
Getting Started with Ruby Modules
[ruby] Method call with argument
Handle dates with Javascript (moment.js)