← 記事

2022年:英語がプログラミング言語になった年

要約:数行の文章を打ち込むだけで付箋アプリを作りました。最初のプロンプトと最後の完成したコードだけでも見てみてください。

前回は、メモ帳と、部分的に動くtodo.txtアプリを作りました

今回は付箋アプリを作ります。

※ 以下はChatGPTとの実際のやり取りです。英語で入力したプロンプト(「You」の部分)とエラーメッセージは、英語がそのまま「プログラミング言語」として使われた例なので原文のまま残し、ChatGPTの回答の地の文を日本語に訳しています。コードは元のままです。

AIが生成した付箋アプリ。画面上の付箋の中にテキストが書かれている

### You
Write a sticky notes application in ruby using gtk3. The window should look like post it note so the title bar needs to be invisible. A window should consist on one big text edit component covering the whole window except a space for the button to create a new sticky note. The window should automatically get bigger if the text grows. When a window is created, make a three digit random number called hoge. Automatically when the text edit component is changed write the contents of the sticky note into a file in the current directory with a filename that starts with note and contains the current date, and content of the hoge variable and ends with txt. When a new post it is created, choose one of (#ff7eb9,#ff65a3,#7afcff,#feff9c,#fff740) and set it as background color. On the bottom of the sticky note, make a transparent button, to create a new sticky note. When I click and drag the text edit component it should move the window. the code should be DRY. Just give me the code.

ChatGPT

以下は、GTK3を使ったRubyの付箋アプリケーションの例です。

require 'gtk3'
require 'date'

# Set the background colors for the sticky notes
NOTE_COLORS = ["#ff7eb9", "#ff65a3", "#7afcff", "#feff9c", "#fff740"]

# Generate a random three-digit number
def generate_hoge
  rand(100..999)
end

# Save the contents of the sticky note to a file
def save_note(hoge, contents)
  # Get the current date
  date = DateTime.now.strftime("%Y%m%d")

  # Create the file name
  file_name = "note-#{date}-#{hoge}.txt"

  # Open the file and write the contents
  File.open(file_name, "w") do |file|
    file.write(contents)
  end
end

class StickyNoteWindow < Gtk::Window
  def initialize(hoge)
    # Set the title bar to be invisible
    super(:toplevel)
    set_decorated(false)

    # Set the background color for the sticky note
    self.override_background_color(:normal, Gdk::RGBA.parse(NOTE_COLORS.sample))

    # Create the text edit component
    @text_view = Gtk::TextView.new
    @text_view.set_wrap_mode(:word)

    # Add the text edit component to the window
    add(@text_view)

    # Set up the signal to save the contents of the text edit component
    # when it is changed
    @text_view.signal_connect "changed" do
      save_note(hoge, @text_view.buffer.text)
    end

    # Set up the signal to move the window when the text edit component
    # is clicked and dragged
    @text_view.signal_connect "button-press-event" do |widget, event|
      if event.button == 1
        self.begin_move_drag(event.button, event.x_root, event.y_root, event.time)
      end
    end

    # Set up the signal to create a new sticky note when the button is clicked
    create_button = Gtk::Button.new(:label => "Create new sticky note")
    create_button.signal_connect "clicked" do
      StickyNoteWindow.new(generate_hoge)
    end
    add(create_button)
  end
end

# Create the initial sticky note window
StickyNoteWindow.new(generate_hoge)

# Start the GTK main loop
Gtk.main

このコードは、タイトルバーが非表示のGTKウィンドウを作成します。ウィンドウ全体をテキスト編集コンポーネントが占め、下部に新しい付箋を作るボタン用のスペースがあります。テキスト編集コンポーネントが変更されると、付箋の内容がカレントディレクトリのファイルに保存されます。ファイル名には現在の日付と3桁の乱数(hogeと呼びます)が含まれます。ウィンドウの背景色は、あらかじめ定義した色のリストから選ばれます。テキスト編集コンポーネントをクリックしてドラッグすると、ウィンドウを移動できます。ウィンドウ下部の

You

Continue

ChatGPT

ボタンをクリックすると、新しい3桁の乱数を使って新しい付箋ウィンドウが作成されます。

お役に立てば幸いです。ご質問があれば、お知らせください。

You

Find errors in the code

ChatGPT

先ほどのコードには、いくつか誤りがあります。

  1. set_decoratedメソッドには真偽値を渡す必要がありますが、コードではシンボル:toplevelが渡されています。set_decorated(false)に変更する必要があります。
  2. override_background_colorメソッドは、状態と色の2つの引数を取ります。コードでは色の引数しか渡されていません。self.override_background_color(:normal, Gdk::RGBA.parse(NOTE_COLORS.sample))に変更する必要があります。
  3. 「changed」シグナルのsignal_connectメソッドにブロックがありません。ブロックを正しく定義するには、@text_view.signal_connect “changed” doに変更する必要があります。
  4. addメソッドがcreate_buttonウィジェットに対して呼ばれていますが、このメソッドはGtk::Windowが継承しているGtk::Containerクラスにしか定義されていません。create_buttonウィジェットは、ウィンドウに追加する前にGtk::Boxなどのコンテナに追加する必要があります。

修正したコードは次のとおりです。

    ...
    set_decorated(false)

    # Set the background color for the sticky note
    self.override_background_color(:normal, Gdk::RGBA.parse(NOTE_COLORS.sample))

    # Create the text edit component
    @text_view = Gtk::TextView.new
    @text_view.set_wrap_mode(:word)

    # Add the text edit component to the window
    add(@text_view)

    # Set up the signal to save the contents of the text edit component
    # when it is changed
    @text_view.signal_connect "changed" do
      save_note(hoge, @text_view.buffer.text)
    end
    ...
    
    # Create a container to hold the button
    button_container = Gtk::Box.new(:vertical)

    # Set up the signal to create a new sticky note when the button is clicked
    create_button = Gtk::Button.new(:label => "Create new sticky note")
    create_button.signal_connect "clicked" do
    StickyNoteWindow.new(generate_hoge)
    end
    button_container.add(create_button)

    # Add the container to the window
    add(button_container)

    end
    end

    # Create the initial sticky note window

    StickyNoteWindow.new(generate_hoge)

    # Start the GTK main loop

    Gtk.main

お役に立てば幸いです。ご質問があれば、お知らせください。

You

Hmm, there is no changed event

ChatGPT

おっしゃるとおり、GTKには「changed」イベントはありません。代わりに、Gtk::TextViewウィジェットの「buffer-changed」シグナルを使えば、テキスト編集コンポーネントの変更を検出できます。

修正したコードは次のとおりです。
…

You

I think you mean key-release-event
Just give me the corrected code.
Also I’m getting the following error.
“Gtk-WARNING **: 18:59:23.397: Attempting to add a widget with type GtkBox to a GtkWindow, but as a GtkBin subclass a GtkWindow can only contain one widget at a time; it already contains a widget of type GtkTextView”

ChatGPT

おっしゃるとおり、テキスト編集コンポーネントの変更を検出するには「key-release-event」シグナルを使うべきです。修正したコードは次のとおりです。

require 'gtk3'
require 'date'

# Set the background colors for the sticky notes
NOTE_COLORS = ["#ff7eb9", "#ff65a3", "#7afcff", "#feff9c", "#fff740"]

# Generate a random three-digit number
def generate_hoge
  rand(100..999)
end

# Save the contents of the sticky note to a file
def save_note(hoge, contents)
  # Get the current date
  date = DateTime.now.strftime("%Y%m%d")

  # Create the file name
  file_name = "note-#{date}-#{hoge}.txt"

  # Open the file and write the contents
  File.open(file_name, "w") do |file|
    file.write(contents)
  end
end

class StickyNoteWindow < Gtk::Window
  def initialize(hoge)
    # Set the title bar to be invisible
    super()
    set_decorated(false)

    # Set the background color for the sticky note
    self.override_background_color(:normal, Gdk::RGBA.parse(NOTE_COLORS.sample))

    # Create the text edit component
    @text_view = Gtk::TextView.new
    @text_view.set_wrap_mode(:word)

    # Add the text edit component to the window
    add(@text_view)

    # Set up the signal to save the contents of the text edit component
    # when a key is released
    @text_view.signal_connect "key-release-event" do
      save_note(hoge, @text_view.buffer.text)
    end

    # Set up the signal to move the window when the text edit component
    # is clicked and dragged
    @text_view.signal_connect "button-press-event" do |widget, event|
      if event.button == 1
        self.begin_move_drag(event.button, event.x_root, event.y_root, event.time)
      end
    end

    # Create a container to hold the button
    button_container = Gtk::Box.new(:vertical)

    # Set up the signal to create a new sticky note when the button is clicked
    create_button = Gtk::Button.new(:label => "Create new sticky note")
    create_button.signal_connect "clicked" do
      StickyNoteWindow.new(generate_hoge)
    end
    button_container.add(create_button)

    # Add the container to the window
    add(button_container)
  end
end

# Create the initial sticky note window
StickyNoteWindow.new(generate_hoge)

# Start the GTK main loop
Gtk.main

受け取っているエラーについては、ウィンドウに複数のウィジェットを追加しようとしていることが原因のようです。Gtk::Windowには一度に1つのウィジェットしか入れられないので、複数のウィジェットを入れるには`Gtk

You

continue

ChatGPT

::Box`のようなコンテナを使う必要があります。

このエラーを解決するには、コードを次のように変更します。

require 'gtk3'
require 'date'

# Set the background colors for the sticky notes
NOTE_COLORS = ["#ff7eb9", "#ff65a3", "#7afcff", "#feff9c", "#fff740"]

# Generate a random three-digit number
def generate_hoge
  rand(100..999)
end

# Save the contents of the sticky note to a file
def save_note(hoge, contents)
  # Get the current date
  date = DateTime.now.strftime("%Y%m%d")

  # Create the file name
  file_name = "note-#{date}-#{hoge}.txt"

  # Open the file and write the contents
  File.open(file_name, "w") do |file|
    file.write(contents)
  end
end

class StickyNoteWindow < Gtk::Window
  def initialize(hoge)
    # Set the title bar to be invisible
    super()
    set_decorated(false)

    # Set the background color for the sticky note
    self.override_background_color(:normal, Gdk::RGBA.parse(NOTE_COLORS.sample))

    # Create a container to hold the text edit component and the button
    container = Gtk::Box.new(:vertical)

    # Create the text edit component
    @text_view = Gtk::TextView.new
    @text_view.set_wrap_mode(:word)

    # Add the text edit component to the container
    container.add(@text_view)

    # Set up the signal to save the contents of the text edit component
    # when a key is released
    @text_view.signal_connect "key-release-event" do
    save_note(hoge, @text_view.buffer.text)
    end

    # Set up the signal to move the window when the text edit component
    # is clicked and dragged
    @text_view.signal_connect "button-press-event" do |widget, event|
    if event.button == 1
        self.begin_move_drag(event.button, event.x_root, event.y_root, event.time)
    end
    end

    # Create the button
    create_button = Gtk::Button.new(:label => "Create new sticky note")
    create_button.signal_connect "clicked" do
    StickyNoteWindow.new(generate_hoge)
    end

    # Add the button to the container
    container.add(create_button)

    # Add the container to the window
    add(container)
    end
    end

    # Create the initial sticky note window

    StickyNoteWindow.new(generate_hoge)

    # Start the GTK main loop

    Gtk.main

お役に立てば幸いです。ご質問があれば、お知らせください。

You

No Window opens

ChatGPT

コードに、ウィンドウが表示されるのを妨げている問題があるようです。

考えられる問題の1つは、superの呼び出しより前にset_decoratedメソッドが呼ばれていることです。Gtk::WindowクラスはGtk::Binのサブクラスで、Gtk::Binクラスでは、ほかのメソッドを呼ぶ前に親クラスのinitializeメソッドを呼ぶ必要があります。

これを直すには、set_decoratedの呼び出しをsuperの呼び出しの後ろに移します。修正したコードは次のとおりです。
…

You

I think
win.signal_connect(“destroy”) { Gtk.main_quit }
win.show_all
are missing

ChatGPT

おっしゃるとおり、コードにはsignal_connectメソッドとshow_allメソッドが抜けています。

signal_connectメソッドは「destroy」シグナルのハンドラーを設定するのに使います。このシグナルはウィンドウが閉じられたときに発生します。シグナルが発生するとGtk.main_quitメソッドが呼ばれ、GTKのメインループが終了してプログラムが終わります。

show_allメソッドは、ウィンドウ内のすべてのウィジェットを表示するのに使います。

修正したコードは次のとおりです。

 ...
    signal_connect("destroy") { Gtk.main_quit }

    # Show all the widgets in the window
    show_all
end
end

# Create the initial sticky note window

StickyNoteWindow.new(generate_hoge)

# Start the GTK main loop

Gtk.main

お役に立てば幸いです。ほかにご質問があれば、お知らせください。

You

Ok great. However, I cannot see the background color. Make the text edit component transparent. Also please make the window 5 lines high when it is created.

ChatGPT

テキスト編集コンポーネントを透明にするには、opacityプロパティを0に設定します。これでコンポーネントは完全に透明になります。

また、テキスト編集コンポーネントのheightプロパティを設定すれば、何行分の高さにするかを指定できます。

変更したコードは次のとおりです。

    ...
    # Create the text edit component
    @text_view = Gtk::TextView.new
    @text_view.set_wrap_mode(:word)
    @text_view.set_opacity(0)
    @text_view.set_height(5)
    ...

お役に立てば幸いです。ほかにご質問があれば、お知らせください。

You

Nono, instead of opaque text edit. Set the background color of the text edit and the background color of the button to the same as the window background color. Also make sure that the window is at least 400 pixel tall and grows with the text edit. Remove the set_height of the text edit.

ChatGPT

テキスト編集コンポーネントとボタンの背景色をウィンドウと同じ色にするには、override_background_colorメソッドを使います。このメソッドは、ウィジェットの状態(この場合は:normal)と、色を表すGdk::RGBAオブジェクトの2つの引数を取ります。

ウィンドウの高さを最低400ピクセルにして、テキスト編集コンポーネントに合わせて大きくなるようにするには、コンテナのset_size_requestメソッドでコンテナの最小の高さを指定します。また、テキスト編集コンポーネントのset_vexpandプロパティを使えば、コンテナ内の空きスペースいっぱいに広げられます。

変更したコードは次のとおりです。

require 'gtk3'
require 'date'

# Set the background colors for the sticky notes
NOTE_COLORS = ["#ff7eb9", "#ff65a3", "#7afcff", "#feff9c", "#fff740"]

# Generate a random three-digit number
def generate_hoge
  rand(100..999)
end

# Save the contents of the sticky note to a file
def save_note(hoge, contents)
  # Get the current date
  date = DateTime.now.strftime("%Y%m%d")

  # Create the file name
  file_name = "note-#{date}-#{hoge}.txt"

  # Open the file and write the contents
  File.open(file_name, "w") do |file|
    file.write(contents)
  end
end

class StickyNoteWindow < Gtk::Window
  def initialize(hoge)
    # Set the title bar to be invisible
    super()
    set_decorated(false)

    # Set the background color for the sticky note
    bg_color = Gdk::RGBA.parse(NOTE_COLORS.sample)
    self.override_background_color(:normal, bg_color)

    # Create a container to hold the text edit component and the button
    container = Gtk::Box.new(:vertical)
    container.set_size_request(400, -1)

    # Create the text edit component
    @text_view = Gtk::TextView.new
    @text_view.set_wrap_mode(:word)
    @text_view.override_background_color(:normal, bg_color)
    @text_view.set_vexpand(true)

    # Add the text edit component to the container
    container.add(@text_view)

    # Set up the signal to save the contents of the text edit component
    # when a key is released
    @text_view.signal_connect "key-release-event" do
      save_note(hoge, @text_view.buffer.text)
end

# Set up the signal to move the window when the text edit component
# is clicked and dragged
@text_view.signal_connect "button-press-event" do |widget, event|
  if event.button == 1
    self.begin_move_drag(event.button, event.x_root, event.y_root, event.time)
  end
end

# Create the button
create_button = Gtk::Button.new(:label => "Create new sticky note")
create_button.override_background_color(:normal, bg_color)
create_button.signal_connect "clicked" do
  StickyNoteWindow.new(generate_hoge)
end

# Add the button to the container
container.add(create_button)

# Add the container to the window
add(container)

# Set up the signal to exit the GTK main loop when the window is closed
signal_connect("destroy") { Gtk.main_quit }

# Show all the widgets in the window
show_all

end
end

# Create the initial sticky note window

StickyNoteWindow.new(generate_hoge)

#Start the GTK main loop

Gtk.main

お役に立てば幸いです。ほかにご質問があれば、お知らせください。

You

ありがとう、ChatGPT。いったいどうしてこれでうまくいったのでしょうか。

You

How do I make the font much bigger?

ChatGPT

テキスト編集コンポーネントのフォントを大きくするには、override_fontメソッドを使います。このメソッドは、使用するフォントを表すPango::FontDescriptionオブジェクトを引数に取ります。

Pango::FontDescriptionオブジェクトはPango::FontDescription.newメソッドで作成でき、フォントサイズはsize=メソッドで設定します。サイズはポイント単位で指定し、72ポイントが1インチに相当します。

テキスト編集コンポーネントのフォントを大きくする例です。

font_description = Pango::FontDescription.new
font_description.size = 36
@text_view.override_font(font_description)

sizeプロパティに別の値を設定すれば、フォントサイズを好みに合わせて調整できます。

お役に立てば幸いです。ほかにご質問があれば、お知らせください。

You

I can’t see any font now

ChatGPT

フォントサイズを設定した後でテキスト編集コンポーネントの文字が見えない場合は、文字色が背景色と同じ色になっている可能性があります。

文字色を設定するには、テキストビューのoverride_colorメソッドを使います。このメソッドは、ウィジェットの状態(この場合は:normal)と、色を表すGdk::RGBAオブジェクトの2つの引数を取ります。

文字色を黒にする例です。

text_color = Gdk::RGBA.parse("#000000")
@text_view.override_color(:normal, text_color)

Gdk::RGBAオブジェクトに別の色を設定すれば、文字色を好みに合わせて調整できます。

お役に立てば幸いです。ほかにご質問があれば、お知らせください。

You

I still cannot see the font

ChatGPT

文字色を設定した後もテキスト編集コンポーネントの文字が見えない場合は、フォントサイズがウィジェットに対して大きすぎる可能性があります。

この問題を切り分けるには、フォントサイズを小さくして文字が見えるようになるか試してみてください。小さくすると見えるようになるなら、希望のサイズで表示できるように、ウィジェットのサイズを大きくする必要があるかもしれません。

ウィジェットのサイズはset_size_requestメソッドで設定できます。このメソッドは、ウィジェットの幅と高さをピクセル単位で指定する2つの引数を取ります。

たとえば、ウィジェットのサイズを800x600ピクセルにするには、次のコードを使います。

@text_view.set_size_request(800, 600)

お役に立てば幸いです。ほかにご質問があれば、お知らせください。

You

I think it’s the font description. How do I set the default font?

ChatGPT

テキスト編集コンポーネントにデフォルトのフォントを設定するには、override_fontメソッドに、サイズやスタイルを指定していないPango::FontDescriptionオブジェクトを渡します。

デフォルトのフォントを表すPango::FontDescriptionオブジェクトは、Pango::FontDescription.newメソッドを引数なしで呼べば作成できます。

テキスト編集コンポーネントにデフォルトのフォントを設定する例です。

font_description = Pango::FontDescription.new
@text_view.override_font(font_description)

あるいは、Pango::FontDescription.from_stringメソッドを使って、フォントを表す文字列からPango::FontDescriptionオブジェクトを作成することもできます。

たとえば、フォントを「Sans 12」にするには、次のコードを使います。

font_description = Pango::FontDescription.from_string("Sans 12")
@text_view.override_font(font_description)

お役に立てば幸いです。ほかにご質問があれば、お知らせください。

You

ありがとう、ChatGPT。これがうまくいったなんて、正直まだ信じられません。
コツは、自分が何を求めているのかをうまく言葉にすることです。ChatGPTは自分からいろいろなことを考え出してくれました。たとえば最初は背景色を正しく設定してくれませんでしたが、それは私の頼み方がまずかったからです。
この技術には本当に期待しています。Sam Altmanに求職のメール(ChatGPTが生成したもの)まで送ってしまいました。返事が来たら、これがどれだけすごいかわかるでしょう。

人間による変更を最小限にとどめた最終コード(フォントのみ)

require 'gtk3'
require 'date'

# Set the background colors for the sticky notes
NOTE_COLORS = ["#ff7eb9", "#ff65a3", "#7afcff", "#feff9c", "#fff740"]

# Generate a random three-digit number
def generate_hoge
  rand(100..999)
end

# Save the contents of the sticky note to a file
def save_note(hoge, contents)
  # Get the current date
  date = DateTime.now.strftime("%Y%m%d")

  # Create the file name
  file_name = "note-#{date}-#{hoge}.txt"

  # Open the file and write the contents
  File.open(file_name, "w") do |file|
    file.write(contents)
  end
end

class StickyNoteWindow < Gtk::Window
  def initialize(hoge)
    # Set the title bar to be invisible
    super()
    set_decorated(false)

    # Set the background color for the sticky note
    bg_color = Gdk::RGBA.parse(NOTE_COLORS.sample)
    self.override_background_color(:normal, bg_color)

    # Create a container to hold the text edit component and the button
    container = Gtk::Box.new(:vertical)
    container.set_size_request(250, 250)

    # Create the text edit component
    @text_view = Gtk::TextView.new
    @text_view.set_wrap_mode(:word)
    @text_view.override_background_color(:normal, bg_color)
    @text_view.set_vexpand(true)

    # font_description = Pango::FontDescription.new
    # font_description.size = 8
    # @text_view.override_font(font_description)
    font_description = Pango::FontDescription.from_string("Comic Sans MS 12")
    @text_view.override_font(font_description)
    
    text_color = Gdk::RGBA.parse("#000000")
    @text_view.override_color(:normal, text_color)

    # Add the text edit component to the container
    container.add(@text_view)

    # Set up the signal to save the contents of the text edit component
    # when a key is released
    @text_view.signal_connect "key-release-event" do
      save_note(hoge, @text_view.buffer.text)
end

# Set up the signal to move the window when the text edit component
# is clicked and dragged
@text_view.signal_connect "button-press-event" do |widget, event|
  if event.button == 1
    self.begin_move_drag(event.button, event.x_root, event.y_root, event.time)
  end
end

# Create the button
create_button = Gtk::Button.new(:label => "Create new sticky note")
create_button.override_background_color(:normal, bg_color)
create_button.signal_connect "clicked" do
  StickyNoteWindow.new(generate_hoge)
end

font_description = Pango::FontDescription.from_string("Comic Sans MS 12")
create_button.override_font(font_description)


# Add the button to the container
container.add(create_button)

# Add the container to the window
add(container)

# Set up the signal to exit the GTK main loop when the window is closed
signal_connect("destroy") { Gtk.main_quit }

# Show all the widgets in the window
show_all

end
end

# Create the initial sticky note window
StickyNoteWindow.new(generate_hoge)

# Start the GTK main loop

Gtk.main