<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE language SYSTEM "klayout_doc.dtd">

<doc>

  <title>Events And Callbacks</title>
  <keyword name="Events"/>
  <keyword name="Callbacks"/>

  <h2-index/>

  <h2>Introduction</h2>

  <p>
  In some places, the API requires to attach code to an event. An event could be a menu item which is selected
  or a change of some status which might require some action. The API allows implementation of specific code which is
  called in that case. This enables us to implement the functionality
  behind a menu item. In this text we will refer to such functionality by the general term "callback". In general
  a callback is custom code that is called from the API in contrast to API code that is called from the custom code.
  </p>

  <p>
  There are basically two ways to attach specific code to a callback:
  </p>

  <ul>
    <li><b>Reimplementation</b>: some API classes provide "virtual" methods. "virtual" is a C++ term and 
    means a method that can be overridden in a derived class. This technique is employed for example in the
    "Strategy" design pattern. In strictly typed C++ this is quite a common pattern which allows definition of interfaces 
    and concrete implementations based on those interfaces. Ruby as a dynamic language doesn't care much about
    classes and their relationship: an object either has a method or it hasn't. So, reimplementation is just a matter
    of providing the right method. An examples for the strategy pattern is the BrowserSource class (<class_doc href="BrowserSource"/>).
    </li>

    <li><b>Events</b>: events allow attaching a piece of code to an event. In Ruby, such a block is "Proc" object, in "Python" it is a
    "callable" object ("lambda function" is a term used in both languages for this kind of concept). 
    In case the event is triggered, this attached code is executed. Multiple lambda functions can be attached to the same event and 
    removed from the latter. Events can be cleared of attached code, where only the blocks attached from one language can be cleared
    together - code attached from Python cannot be cleared from Ruby.
    An example for events is the Action class (<class_doc href="Action"/>) which provides both the reimplementation interface 
    ("triggered" method) and the event interface ("on_triggered"). By the way, Qt signals are mapped to events in KLayout's Qt 
    binding (<link href="/programming/qt_binding.xml"/>).
    </li>
  </ul>

  <p>
  The "Observer" class which was there prior to KLayout 0.25 has been dropped in favour of the more
  flexible events. It is no longer supported.
  </p>

  <h2>Reimplementation (Strategy Pattern)</h2>

  <p>
  The BrowserSource (<class_doc href="BrowserSource"/>) class is a nice example for the Strategy pattern.
  It is used by the BrowserDialog class (<class_doc href="BrowserDialog"/>) as a kind of internal HTML server
  which handles URL's starting with "int:". For this, a script has to provide a class that reimplements the "get(url)"
  method. In the following example, a BrowserSource is created that takes an URL with an integer index number and
  delivers a HTML text with a link to the URL with the next index.
  </p>

  <p>
  Here is the code.
  This example demonstrates how the "get" method is reimplemented to deliver the actual text.
  </p>

  <pre>
module MyMacro
  
  include RBA
  
  class MyBrowserSource &lt; BrowserSource
    def get(url)
      next_url = url.sub(/\d+/) { |num| (num.to_i+1).to_s }
      "This is #{url}. &lt;a href='#{next_url}'&gt;Goto next (#{next_url})&lt;/a&gt;"
    end
  end
  
  dialog = BrowserDialog::new
  dialog.source = MyBrowserSource::new
  dialog.home = "int:0"
  dialog.exec

end
</pre>

  The Python version is this:

  <pre>
from pya import BrowserSource, BrowserDialog

class MyBrowserSource(BrowserSource):
  def get(self, url):
    next_url = "int:" + str(int(url.split(":")[1]) + 1)
    return f"This is {url}. &lt;a href='{next_url}'&gt;Goto next ({next_url})&lt;/a&gt;>"

dialog = BrowserDialog()
dialog.home = "int:0"
dialog.source = MyBrowserSource()
dialog.exec_()
</pre>

  <p>
  Ruby even allows reimplementation of a method without deriving a new class, because it allows
  defining methods per instance:
  </p>

  <pre>
module MyMacro
  
  include RBA
  
  source = BrowserSource::new
  def source.get(url)
    next_url = url.sub(/\d+/) { |num| (num.to_i+1).to_s }
    "This is #{url}. &lt;a href='#{next_url}'&gt;Goto next (#{next_url})&lt;/a&gt;"
  end
  
  dialog = BrowserDialog::new
  dialog.source = source
  dialog.home = "int:0"
  dialog.exec

end
</pre>

  <h2>Events</h2>

  <p>
  Events are the callback variant which is the easiest one to use. Using an event it is possible
  to directly attach a block of code to a callback. An event has a specific signature, i.e. 
  the parameters it provides. The block can obtain this parameters by listing them in its argument list.
  </p>

  <p>
  Here is a simple example that uses the parameterless "on_triggered" event of the Action
  class (<class_doc href="Action"/>). It puts a new entry into the tool bar and if it is 
  clicked, it displays a message box:
  </p>

  <pre>
module MyMacro
  
  include RBA
  
  action = Action::new
  action.on_triggered do
    MessageBox::info("A message", "The action was triggered", MessageBox::Ok)
  end
  action.title = "My Action"
  
  Application::instance.main_window.menu.insert_item("@toolbar.end", "my_action", action)
  
end
</pre>

  <p>
  The Python version is:
  </p>

  <pre>
from pya import Action, MessageBox, Application

def on_triggered():
  MessageBox.info("A message", "The action was triggered", MessageBox.Ok)

action = Action()
action.on_triggered = on_triggered
action.title = "My Action"

Application.instance().main_window().menu().insert_item("@toolbar.end", "my_action", action)
</pre>

  <p>
  Specifying a block to an event will make the event only execute that block. A more flexible way of 
  controlling the code attached to events is available through the += and -= operators:
  </p>

  <pre>
module MyMacro

  include RBA

  code = lambda do 
    MessageBox::info("A message", "The action was triggered", MessageBox::Ok)
  end

  action = Action::new
  action.on_triggered += code

  ...
  
  # to remove the code from the event, use:
  action.on_triggered -= code

  # to replace all event handlers by the one given by "code":
  action.on_triggered = code

  # to clear all event handlers use:
  action.on_triggered.clear
</pre>

  <p>
  Synonyms for the <tt>+=</tt> operator are <tt>add</tt> and <tt>connect</tt>. The latter makes code more familiar for PyQt users.
  In the same way, synonyms for the <tt>-=</tt> operator are <tt>remove</tt> and <tt>disconnect</tt>.
  </p>

  <p>
  If the Qt binding is available (see <link href="/programming/qt_binding.xml"/>), Qt signals
  are implemented as events. This way it's very simple to create a Qt dialog. In following example,
  the "textChanged" signal of QLineEdit is attached a code block which copies the text of the 
  input field to the label below:
  </p>
  
  <pre>
module MyMacro
  
  include RBA
  
  dialog = QDialog::new(Application::instance.main_window)
  layout = QVBoxLayout::new(dialog)
  input = QLineEdit::new(dialog)
  label = QLabel::new(dialog)
  layout.addWidget(input)
  layout.addWidget(label)

  # implement the textChanged signal as event:
  input.textChanged { |text| label.text = text }

  dialog.exec

end
</pre>

  <p>
  The Python version is:
  </p>

  <pre>
from pya import QDialog, QVBoxLayout, QLineEdit, QLabel, Application

dialog = QDialog(Application.instance().main_window())
layout = QVBoxLayout(dialog)
input = QLineEdit(dialog)
label = QLabel(dialog)
layout.addWidget(input)
layout.addWidget(label)

def text_changed(text):
  label.text = text

# implement the textChanged signal as event:
input.textChanged = text_changed

dialog.exec_()
</pre>

  <p>
  Using the += operator on the event, multiple handlers can be added to a signal:
  </p>

  <pre>
module MyMacro
  
  include RBA
  
  dialog = QDialog::new(Application::instance.main_window)
  layout = QVBoxLayout::new(dialog)
  input = QLineEdit::new(dialog)
  label1 = QLabel::new(dialog)
  label2 = QLabel::new(dialog)
  layout.addWidget(input)
  layout.addWidget(label1)
  layout.addWidget(label2)

  # two signal consumers:
  input.textChanged += lambda { |text| label1.text = text }
  input.textChanged += lambda { |text| label2.text = text.reverse }
  
  dialog.exec

end
</pre>

  <p>
  with the Python version:
  </p>

  <pre>
from pya import QDialog, QVBoxLayout, QLineEdit, QLabel, Application

dialog = QDialog(Application.instance().main_window())
layout = QVBoxLayout(dialog)
input = QLineEdit(dialog)
label1 = QLabel(dialog)
label2 = QLabel(dialog)
layout.addWidget(input)
layout.addWidget(label1)
layout.addWidget(label2)

def text_changed1(text):
  label1.text = text

def text_changed2(text):
  label2.text = text[::-1]

# two signal consumers:
input.textChanged += text_changed1
input.textChanged += text_changed2

dialog.exec_()
</pre>

</doc>

