mirror of https://github.com/KLayout/klayout.git
Adding samples for new plugin features, doc update.
This commit is contained in:
parent
e396b6ec29
commit
f1c16a0242
|
|
@ -976,7 +976,7 @@ marker.destroy</pre>
|
|||
<p>
|
||||
The PluginFactory itself acts as a singleton per plugin class and provides not only the ability to create
|
||||
Plugin objects but also a couple of configuration options and a global handler for configuration and menu
|
||||
events. The configuration includes:
|
||||
events. The PluginFactory provides:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
|
|
@ -991,6 +991,10 @@ marker.destroy</pre>
|
|||
After an option is configured, the individual Plugin objects and the PluginFactory receives "configure" calls
|
||||
when a configuration option changes or for the initial configuration.
|
||||
</li>
|
||||
<li>Widgets: The plugin factory can provide widgets for the configuration dialog ('File/Setup') and the
|
||||
editor options dock. Respective callbacks are <class_doc href="PluginFactory#create_config_pages"/>
|
||||
and <class_doc href="PluginFactory#create_editor_options_pages"/>.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
|
|
@ -1048,5 +1052,29 @@ marker.destroy</pre>
|
|||
over the mouse in certain circumstances and is supposed to put the plugin into a "watching" instead of "dragging" state.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
A plugin may also create markers for visual feedback and highlights. This can be done explicitly
|
||||
using marker objects (<class_doc href="Marker"/>) or in a application-defined fashion by generating
|
||||
mouse cursors. The API functions for this purpose are <class_doc href="Plugin#clear_mouse_cursors"/>,
|
||||
<class_doc href="Plugin#add_mouse_cursor"/> and <class_doc href="Plugin#add_edge_marker"/>. These
|
||||
functions provide cursors and highlights that match the visual effects of other plugins and
|
||||
interface with the mouse tracking feature of the application.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Another service the Plugin class provides is snapping:
|
||||
there exist a number of global configuration options which control snapping (grids, snapping to
|
||||
objects, angle constraints). The plugin offers a number of snap functions that follow the
|
||||
application's current configuration and implement snapping accordingly. These methods are
|
||||
<class_doc href="Plugin#snap"/> and <class_doc href="Plugin#snap2"/>. While the first
|
||||
method provides grid and angle snapping, the second also implements snapping to layout objects.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The "drag box" sample macro demonstrates many of these features.
|
||||
The sample is available from the macro templates when you create a new
|
||||
macro in the macro IDE.
|
||||
</p>
|
||||
|
||||
</doc>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
<file alias="new_macro.lym">macro_templates/new_macro.lym</file>
|
||||
<file alias="new_text_file.txt">macro_templates/new_text_file.txt</file>
|
||||
<file alias="new_ruby_file.rb">macro_templates/new_ruby_file.rb</file>
|
||||
<file alias="drag_box_sample.lym">macro_templates/drag_box_sample.lym</file>
|
||||
<file alias="drag_box_sample_python.lym">macro_templates/drag_box_sample_python.lym</file>
|
||||
<file alias="pcell.lym">macro_templates/pcell.lym</file>
|
||||
<file alias="pcell_sample.lym">macro_templates/pcell_sample.lym</file>
|
||||
<file alias="qt_designer.lym">macro_templates/qt_designer.lym</file>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,258 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<klayout-macro>
|
||||
<description>A plugin sample\nThis sample provides a box drawing feature and demonstrates UI components and snapping</description>
|
||||
<format>general</format>
|
||||
<autorun>true</autorun>
|
||||
<autorun-early>false</autorun-early>
|
||||
<show-in-menu>false</show-in-menu>
|
||||
<shortcut></shortcut>
|
||||
<interpreter>ruby</interpreter>
|
||||
<text># Sample plugin
|
||||
#
|
||||
# This plugin implements a box that can be drawn by
|
||||
# clicking at the first and then at the second point.
|
||||
# There is one box which is replacing the previous one.
|
||||
# Line color and line width of the box can be configured
|
||||
# by editor options (line width) or configuration pages
|
||||
# (color).
|
||||
|
||||
module DragBox
|
||||
|
||||
# Register this macro as "autorun" to enable the plugin
|
||||
|
||||
CFG_COLOR = "drag-box-color"
|
||||
CFG_WIDTH = "drag-box-width"
|
||||
|
||||
# The widget placed into the editor options dock
|
||||
|
||||
class DragBoxEditorOptionsPage < RBA::EditorOptionsPage
|
||||
|
||||
def initialize
|
||||
|
||||
# Creates a new page with title "Options" and at
|
||||
# position 1 (second from left)
|
||||
super("Options", 1)
|
||||
|
||||
layout2 = RBA::QVBoxLayout::new(self)
|
||||
layout = RBA::QHBoxLayout::new(self)
|
||||
layout2.addLayout(layout)
|
||||
label = RBA::QLabel::new("Line width", self)
|
||||
layout.addWidget(label)
|
||||
@spin_box = RBA::QSpinBox::new(self)
|
||||
@spin_box.setMinimum(1)
|
||||
@spin_box.setMaximum(16)
|
||||
layout.addWidget(@spin_box)
|
||||
layout.addStretch(1)
|
||||
layout2.addStretch(1)
|
||||
@spin_box.valueChanged = lambda { |x| self.edited }
|
||||
|
||||
end
|
||||
|
||||
def setup(dispatcher)
|
||||
begin
|
||||
@spin_box.setValue(dispatcher.get_config(CFG_WIDTH).to_i)
|
||||
rescue
|
||||
@spin_box.setValue(1)
|
||||
end
|
||||
end
|
||||
|
||||
def apply(dispatcher)
|
||||
dispatcher.set_config(CFG_WIDTH, @spin_box.value.to_s)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# The widget placed into the configuration page
|
||||
|
||||
class DragBoxConfigPage < RBA::ConfigPage
|
||||
|
||||
def initialize
|
||||
|
||||
# places the widget on a new section ("Drag Box")
|
||||
# and "Configure" page
|
||||
super("Drag Box|Configure")
|
||||
|
||||
# Qt user interfact setup
|
||||
layout = RBA::QHBoxLayout::new(self)
|
||||
label = RBA::QLabel::new("Color (hex, rrggbb)", self)
|
||||
layout.addWidget(label)
|
||||
@line_edit = RBA::QLineEdit::new(self)
|
||||
layout.addWidget(@line_edit)
|
||||
layout.addStretch(1)
|
||||
|
||||
end
|
||||
|
||||
def setup(dispatcher)
|
||||
@line_edit.setText(dispatcher.get_config(CFG_COLOR))
|
||||
end
|
||||
|
||||
def apply(dispatcher)
|
||||
dispatcher.set_config(CFG_COLOR, @line_edit.text)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class DragBoxPlugin < RBA::Plugin
|
||||
|
||||
def initialize(view)
|
||||
super()
|
||||
@marker = nil
|
||||
@last_box = nil
|
||||
@box = nil
|
||||
@start_point = nil
|
||||
@view = view
|
||||
@color = nil
|
||||
@width = 1
|
||||
end
|
||||
|
||||
def configure(name, value)
|
||||
# receives configuration callbacks
|
||||
if name == CFG_COLOR
|
||||
# configure marker color
|
||||
begin
|
||||
@color = value != "" ? value.to_i(16) : nil
|
||||
rescue
|
||||
self.color = nil
|
||||
end
|
||||
self._configure_marker
|
||||
elsif name == CFG_WIDTH
|
||||
# configure marker line width
|
||||
begin
|
||||
@width = value.to_i
|
||||
rescue
|
||||
@width = nil
|
||||
end
|
||||
self._configure_marker
|
||||
end
|
||||
end
|
||||
|
||||
def _clear_marker
|
||||
# clears all markers
|
||||
[ @marker, @last_box ].each { |m| m && m._destroy }
|
||||
@marker = nil
|
||||
@last_box = nil
|
||||
end
|
||||
|
||||
def _update_marker
|
||||
# updates the marker with the current box
|
||||
if !@marker
|
||||
@marker = RBA::Marker::new(self.view)
|
||||
self._configure_marker
|
||||
end
|
||||
@marker.set(@box)
|
||||
end
|
||||
|
||||
def freeze_marker
|
||||
# stop dragging the marker and copy to a static one
|
||||
if @last_box
|
||||
@last_box._destroy()
|
||||
end
|
||||
@last_box = @marker
|
||||
@marker = nil
|
||||
end
|
||||
|
||||
def _configure_marker
|
||||
# change the marker's appearance
|
||||
if @marker
|
||||
@marker.line_style = 2 # short-dashed
|
||||
@marker.vertex_size = 0 # no vertexes
|
||||
@marker.line_width = @width
|
||||
@marker.color = @color ? (@color | 0xff000000) : 0
|
||||
end
|
||||
end
|
||||
|
||||
def activated
|
||||
# plugin is activated - i.e. the mode is selected
|
||||
RBA::MainWindow.instance.message("Click on point to start dragging a box", 10000)
|
||||
end
|
||||
|
||||
def deactivated
|
||||
# plugin is deactivated - i.e. the mode is unselected
|
||||
self._clear_marker
|
||||
RBA::MainWindow.instance.message("", 0)
|
||||
end
|
||||
|
||||
def mouse_click_event(p, buttons, prio)
|
||||
if prio
|
||||
# first-level event: start a new box or
|
||||
# stop dragging it and freeze the box
|
||||
if !@marker
|
||||
p = self.snap2(p)
|
||||
@box = RBA::DBox::new(p, p)
|
||||
@start_point = p
|
||||
self._clear_marker
|
||||
self._update_marker
|
||||
self.grab_mouse
|
||||
RBA::MainWindow.instance.message("Drag the box and click again", 10000)
|
||||
else
|
||||
p = self.snap2(p, @start_point, true, self.ac_from_buttons(buttons))
|
||||
self.freeze_marker
|
||||
self.ungrab_mouse
|
||||
RBA::MainWindow.instance.message("Box finished: " + @box.to_s, 10000)
|
||||
end
|
||||
# consume event
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def mouse_moved_event(p, buttons, prio)
|
||||
if prio
|
||||
# first-level event: if not dragging, provide a
|
||||
# mouse cursor for tracking. If dragging, update
|
||||
# the box and provide a mouse cursor.
|
||||
if !@marker
|
||||
self.clear_mouse_cursors
|
||||
p = self.snap2(p, true)
|
||||
self.add_mouse_cursor(p)
|
||||
else
|
||||
self.clear_mouse_cursors
|
||||
p = self.snap2(p, @start_point, true, self.ac_from_buttons(buttons), true)
|
||||
self.add_mouse_cursor(p)
|
||||
@box = RBA::DBox::new(@start_point, p)
|
||||
self._update_marker
|
||||
end
|
||||
end
|
||||
# NOTE: we must not digest this event (i.e. return true)
|
||||
# to allow the mouse tracker to receive the events as well
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Implements a "plugin factory".
|
||||
# Purpose of this object is to create a plugin object
|
||||
# and corresponding UI objects.
|
||||
|
||||
class DragBoxPluginFactory < RBA::PluginFactory
|
||||
|
||||
def initialize
|
||||
super()
|
||||
self.has_tool_entry = true
|
||||
# NOTE: it's a good practice to register configuration options
|
||||
self.add_option(CFG_WIDTH, "1")
|
||||
self.add_option(CFG_COLOR, "")
|
||||
self.register(-1000, "drag_box", "Drag Box")
|
||||
end
|
||||
|
||||
def create_config_pages
|
||||
self.add_config_page(DragBoxConfigPage::new)
|
||||
end
|
||||
|
||||
def create_editor_options_pages
|
||||
self.add_editor_options_page(DragBoxEditorOptionsPage::new)
|
||||
end
|
||||
|
||||
def create_plugin(manager, root, view)
|
||||
return DragBoxPlugin::new(view)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Create the singleton instance - as we register it,
|
||||
# it is not garbage collected
|
||||
DragBoxPluginFactory::new
|
||||
|
||||
end</text>
|
||||
</klayout-macro>
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<klayout-macro>
|
||||
<description>A plugin sample\nThis sample provides a box drawing feature and demonstrates UI components and snapping</description>
|
||||
<format>general</format>
|
||||
<autorun>true</autorun>
|
||||
<autorun-early>false</autorun-early>
|
||||
<show-in-menu>false</show-in-menu>
|
||||
<shortcut></shortcut>
|
||||
<interpreter>python</interpreter>
|
||||
<text># Sample plugin
|
||||
#
|
||||
# This plugin implements a box that can be drawn by
|
||||
# clicking at the first and then at the second point.
|
||||
# There is one box which is replacing the previous one.
|
||||
# Line color and line width of the box can be configured
|
||||
# by editor options (line width) or configuration pages
|
||||
# (color).
|
||||
|
||||
# Register this macro as "autorun" to enable the plugin
|
||||
|
||||
cfg_color = "drag-box-color"
|
||||
cfg_width = "drag-box-width"
|
||||
|
||||
# The widget placed into the editor options dock
|
||||
|
||||
class DragBoxEditorOptionsPage(pya.EditorOptionsPage):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# Creates a new page with title "Options" and at
|
||||
# position 1 (second from left)
|
||||
super(DragBoxEditorOptionsPage, self).__init__("Options", 1)
|
||||
|
||||
layout2 = pya.QVBoxLayout(self)
|
||||
layout = pya.QHBoxLayout(self)
|
||||
layout2.addLayout(layout)
|
||||
label = pya.QLabel("Line width", self)
|
||||
layout.addWidget(label)
|
||||
self.spin_box = pya.QSpinBox(self)
|
||||
self.spin_box.setMinimum(1)
|
||||
self.spin_box.setMaximum(16)
|
||||
layout.addWidget(self.spin_box)
|
||||
layout.addStretch(1)
|
||||
layout2.addStretch(1)
|
||||
|
||||
self.spin_box.valueChanged = lambda x: self.edited()
|
||||
|
||||
def setup(self, dispatcher):
|
||||
try:
|
||||
self.spin_box.setValue(int(dispatcher.get_config(cfg_width)))
|
||||
except:
|
||||
self.spin_box.setValue(1)
|
||||
|
||||
def apply(self, dispatcher):
|
||||
dispatcher.set_config(cfg_width, str(self.spin_box.value))
|
||||
|
||||
# The widget placed into the configuration page
|
||||
|
||||
class DragBoxConfigPage(pya.ConfigPage):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# places the widget on a new section ("Drag Box")
|
||||
# and "Configure" page
|
||||
super(DragBoxConfigPage, self).__init__("Drag Box|Configure")
|
||||
|
||||
layout = pya.QHBoxLayout(self)
|
||||
label = pya.QLabel("Color (hex, rrggbb)", self)
|
||||
layout.addWidget(label)
|
||||
self.line_edit = pya.QLineEdit(self)
|
||||
layout.addWidget(self.line_edit)
|
||||
layout.addStretch(1)
|
||||
|
||||
def setup(self, dispatcher):
|
||||
self.line_edit.setText(dispatcher.get_config(cfg_color))
|
||||
|
||||
def apply(self, dispatcher):
|
||||
dispatcher.set_config(cfg_color, self.line_edit.text)
|
||||
|
||||
class DragBoxPlugin(pya.Plugin):
|
||||
|
||||
def __init__(self, view):
|
||||
super(DragBoxPlugin, self).__init__()
|
||||
self.marker = None
|
||||
self.last_box = None
|
||||
self.box = None
|
||||
self.start_point = None
|
||||
self.view = view
|
||||
self.color = None
|
||||
self.width = 1
|
||||
|
||||
def configure(self, name, value):
|
||||
# receives configuration callbacks
|
||||
needs_update = False
|
||||
if name == cfg_color:
|
||||
# configure marker color
|
||||
try:
|
||||
if value != "":
|
||||
self.color = int(value, 16)
|
||||
else:
|
||||
self.color = None
|
||||
except:
|
||||
self.color = None
|
||||
self._configure_marker()
|
||||
elif name == cfg_width:
|
||||
# configure marker line width
|
||||
try:
|
||||
self.width = int(value)
|
||||
except:
|
||||
self.width = None
|
||||
self._configure_marker()
|
||||
|
||||
def _clear_marker(self):
|
||||
# clears all markers
|
||||
for marker in [ self.marker, self.last_box ]:
|
||||
if marker is not None:
|
||||
marker._destroy()
|
||||
self.marker = None
|
||||
self.last_box = None
|
||||
|
||||
def _update_marker(self):
|
||||
# updates the marker with the current box
|
||||
if self.marker is None:
|
||||
self.marker = pya.Marker(self.view)
|
||||
self._configure_marker()
|
||||
self.marker.set(self.box)
|
||||
|
||||
def freeze_marker(self):
|
||||
# stop dragging the marker and copy to a static one
|
||||
if self.last_box is not None:
|
||||
self.last_box._destroy()
|
||||
self.last_box = self.marker
|
||||
self.marker = None
|
||||
|
||||
def _configure_marker(self):
|
||||
# change the marker's appearance
|
||||
if self.marker is not None:
|
||||
self.marker.line_style = 2 # short-dashed
|
||||
self.marker.vertex_size = 0 # no vertexes
|
||||
self.marker.line_width = self.width
|
||||
if self.color is not None:
|
||||
self.marker.color = self.color | 0xff000000
|
||||
else:
|
||||
self.marker.color = 0 # auto
|
||||
|
||||
def activated(self):
|
||||
# plugin is activated - i.e. the mode is selected
|
||||
pya.MainWindow.instance().message("Click on point to start dragging a box", 10000)
|
||||
|
||||
def deactivated(self):
|
||||
# plugin is deactivated - i.e. the mode is unselected
|
||||
self._clear_marker()
|
||||
pya.MainWindow.instance().message("", 0)
|
||||
|
||||
def mouse_click_event(self, p, buttons, prio):
|
||||
if prio:
|
||||
# first-level event: start a new box or
|
||||
# stop dragging it and freeze the box
|
||||
if self.marker is None:
|
||||
p = self.snap2(p)
|
||||
self.box = pya.DBox(p, p)
|
||||
self.start_point = p
|
||||
self._clear_marker()
|
||||
self._update_marker()
|
||||
self.grab_mouse()
|
||||
pya.MainWindow.instance().message("Drag the box and click again", 10000)
|
||||
else:
|
||||
p = self.snap2(p, self.start_point, True, self.ac_from_buttons(buttons))
|
||||
self.freeze_marker()
|
||||
self.ungrab_mouse()
|
||||
pya.MainWindow.instance().message("Box finished: " + str(self.box), 10000)
|
||||
return True
|
||||
return False
|
||||
|
||||
def mouse_moved_event(self, p, buttons, prio):
|
||||
if prio:
|
||||
# first-level event: if not dragging, provide a
|
||||
# mouse cursor for tracking. If dragging, update
|
||||
# the box and provide a mouse cursor.
|
||||
if self.marker is None:
|
||||
self.clear_mouse_cursors()
|
||||
p = self.snap2(p, visualize=True)
|
||||
self.add_mouse_cursor(p)
|
||||
else:
|
||||
self.clear_mouse_cursors()
|
||||
p = self.snap2(p, self.start_point, True, self.ac_from_buttons(buttons), visualize=True)
|
||||
self.add_mouse_cursor(p)
|
||||
self.box = pya.DBox(self.start_point, p)
|
||||
self._update_marker()
|
||||
# NOTE: we must not digest this event (i.e. return True)
|
||||
# to allow the mouse tracker to receive the events as well
|
||||
return False
|
||||
|
||||
|
||||
# Implements a "plugin factory".
|
||||
# Purpose of this object is to create a plugin object
|
||||
# and corresponding UI objects.
|
||||
|
||||
class DragBoxPluginFactory(pya.PluginFactory):
|
||||
|
||||
def __init__(self):
|
||||
super(DragBoxPluginFactory, self).__init__()
|
||||
self.has_tool_entry = True
|
||||
# NOTE: it's a good practice to register configuration options
|
||||
self.add_option(cfg_width, "1")
|
||||
self.add_option(cfg_color, "")
|
||||
self.register(-1000, "drag_box", "Drag Box")
|
||||
|
||||
def create_config_pages(self):
|
||||
self.add_config_page(DragBoxConfigPage())
|
||||
|
||||
def create_editor_options_pages(self):
|
||||
self.add_editor_options_page(DragBoxEditorOptionsPage())
|
||||
|
||||
def create_plugin(self, manager, root, view):
|
||||
return DragBoxPlugin(view)
|
||||
|
||||
# Create the singleton instance - as we register it,
|
||||
# it is not garbage collected
|
||||
DragBoxPluginFactory()
|
||||
</text>
|
||||
</klayout-macro>
|
||||
|
|
@ -23,6 +23,7 @@ pcell_sample.lym
|
|||
qt_designer.lym
|
||||
qt_dialog.lym
|
||||
qt_server.lym
|
||||
drag_box_sample.lym
|
||||
|
||||
[pymacros]
|
||||
# General group
|
||||
|
|
@ -41,4 +42,5 @@ pcell_sample_python.lym
|
|||
qt_designer_python.lym
|
||||
qt_dialog_python.lym
|
||||
qt_server_python.lym
|
||||
drag_box_sample_python.lym
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue