mirror of https://github.com/KLayout/klayout.git
444 lines
12 KiB
XML
444 lines
12 KiB
XML
<?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). These settings are managed through configuration
|
|
# options and their current state is persisted.
|
|
#
|
|
# The dimension of the box can be entered numerically
|
|
# while dragging the box. This feature is implemented
|
|
# through a modal "focus page", which opens when you
|
|
# press the Tab key during editing and when the keyboard
|
|
# focus is on the canvas.
|
|
#
|
|
# Register this macro as "autorun" to enable the plugin
|
|
# on startup.
|
|
|
|
cfg_color = "drag-box-color"
|
|
cfg_width = "drag-box-width"
|
|
|
|
# The widget placed into the editor options dock
|
|
|
|
class DragBoxEditorOptionsPage(pya.EditorOptionsPage):
|
|
|
|
"""
|
|
An option page providing a single entry box for configuring the line width
|
|
This page communicates via configuration options. One advantage of this
|
|
approach is that the values are persisted
|
|
"""
|
|
|
|
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)
|
|
|
|
# connect the spin box value change with the "edited" slot
|
|
# which will result in a call of "apply".
|
|
self.spin_box.valueChanged = lambda x: self.edited()
|
|
|
|
def setup(self, dispatcher):
|
|
|
|
"""
|
|
"setup" is called when the page needs to be populated with information -
|
|
i.e. on first show.
|
|
"""
|
|
|
|
try:
|
|
self.spin_box.setValue(int(dispatcher.get_config(cfg_width)))
|
|
except:
|
|
self.spin_box.setValue(1)
|
|
|
|
def apply(self, dispatcher):
|
|
|
|
"""
|
|
"apply" is called when the page is requested to submit the entered
|
|
values to the plugin. Usually this should be done via configuration
|
|
events.
|
|
"""
|
|
|
|
dispatcher.set_config(cfg_width, str(self.spin_box.value))
|
|
|
|
# The modal dialog page that appears when "Tab" is pressed
|
|
|
|
class DragBoxFocusPage(pya.EditorOptionsPage):
|
|
|
|
"""
|
|
A (modal) option page, also called a "focus page". This page is
|
|
registered like an editor options page. It is brought to front
|
|
when the user hits the "Tab" key during editing.
|
|
In this case, this page uses "setup" and "apply" callbacks to
|
|
set and fetch information. It also employs a handler named
|
|
"update_box" to communicate changes between the client (the
|
|
plugin) and the page.
|
|
|
|
Attributes that the client needs to take care of are
|
|
"self.box" (the current box), "self.pfix" (the start point)
|
|
and "self.update_box".
|
|
"""
|
|
|
|
def __init__(self):
|
|
|
|
"""
|
|
Creates a new page with title "Options" and at
|
|
position 1 (second from left)
|
|
"""
|
|
|
|
super(DragBoxFocusPage, self).__init__("Geometry", 2)
|
|
|
|
self.focus_page = True
|
|
self.modal_page = True
|
|
|
|
self.box = pya.DBox()
|
|
self.pfix = pya.DPoint()
|
|
self.update_box = None
|
|
|
|
layout = pya.QGridLayout(self)
|
|
layout.setColumnStretch(1, 1)
|
|
|
|
label = pya.QLabel("Width", self)
|
|
layout.addWidget(label, 0, 0, 1, 1)
|
|
self.le_width = pya.QLineEdit(self)
|
|
layout.addWidget(self.le_width, 0, 1, 1, 1)
|
|
|
|
label = pya.QLabel("Height", self)
|
|
layout.addWidget(label, 1, 0, 1, 1)
|
|
self.le_height = pya.QLineEdit(self)
|
|
layout.addWidget(self.le_height, 1, 1, 1, 1)
|
|
|
|
layout.setRowStretch(2, 1)
|
|
|
|
def setup(self, dispatcher):
|
|
|
|
"""
|
|
Is called when the page needs to be set up.
|
|
We assume that the client has properly set up self.box
|
|
"""
|
|
|
|
self.le_width.text = "%.12g" % self.box.width()
|
|
self.le_height.text = "%.12g" % self.box.height()
|
|
|
|
def apply(self, dispatcher):
|
|
|
|
"""
|
|
Apply is called when the dialog is accepted or the "Apply" button is pressed
|
|
Usually this method is intended to submit configuration parameter changes,
|
|
but we can use it for any other purpose as well.
|
|
"""
|
|
|
|
# fetches the coordinates from the entry boxes
|
|
# throws an exception in case of an error
|
|
x = float(self.le_width.text)
|
|
y = float(self.le_height.text)
|
|
|
|
# prepares a new box with the given dimensions
|
|
# using the initial point ("pfix") and considering
|
|
# the drag direction
|
|
t = b = self.pfix.y
|
|
l = r = self.pfix.x
|
|
|
|
if self.box.bottom < t - 1e-10:
|
|
b = t - y
|
|
else:
|
|
t = b + y
|
|
|
|
if self.box.left < l - 1e-10:
|
|
l = r - x
|
|
else:
|
|
r = l + x
|
|
|
|
# issue the event (call the handler) to inform the plugin of this change
|
|
if self.update_box is not None:
|
|
self.update_box(pya.DBox(l, b, r, t))
|
|
|
|
# The widget placed into the configuration page
|
|
|
|
class DragBoxConfigPage(pya.ConfigPage):
|
|
|
|
"""
|
|
A configuration page with a single entry box to change
|
|
the box color in RGB hex style.
|
|
Configuration pages appear in the Setup dialog and can
|
|
communicate only through configuration parameter updates.
|
|
"""
|
|
|
|
def __init__(self):
|
|
|
|
"""
|
|
Initializes the page. Places it on a new section ("Drag Box") and "Configure" page
|
|
and creates a single entry field.
|
|
"""
|
|
|
|
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):
|
|
"""
|
|
This method is called to request an update of the entry fields
|
|
"""
|
|
self.line_edit.setText(dispatcher.get_config(cfg_color))
|
|
|
|
def apply(self, dispatcher):
|
|
"""
|
|
This method is called to request a transfer of the edited values
|
|
to the configuration space.
|
|
"""
|
|
dispatcher.set_config(cfg_color, self.line_edit.text)
|
|
|
|
class DragBoxPlugin(pya.Plugin):
|
|
|
|
"""
|
|
The custom plugin implementation.
|
|
"""
|
|
|
|
def __init__(self, view):
|
|
super(DragBoxPlugin, self).__init__()
|
|
self.marker = None
|
|
self.last_marker = None
|
|
self.box = None
|
|
self.start_point = None
|
|
self.view = view
|
|
self.color = None
|
|
self.width = 1
|
|
|
|
def configure(self, name, value):
|
|
"""
|
|
This method receives configuration callbacks
|
|
"""
|
|
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_marker ]:
|
|
if marker is not None:
|
|
marker._destroy()
|
|
self.marker = None
|
|
self.last_marker = None
|
|
|
|
def _finish(self):
|
|
"""
|
|
stops dragging the marker and copy to a static one
|
|
"""
|
|
if self.last_marker is not None:
|
|
self.last_marker._destroy()
|
|
self.last_marker = self.marker
|
|
self.marker = None
|
|
# reset to idle
|
|
self.ungrab_mouse()
|
|
pya.MainWindow.instance().message("Box finished: " + str(self.box), 10000)
|
|
|
|
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 _configure_marker(self):
|
|
"""
|
|
changes 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 _update_box(self, box):
|
|
"""
|
|
Updates the box with the given value and updates the marker.
|
|
This method is bound to the focus page handler when needed.
|
|
"""
|
|
self.box = box
|
|
self._update_marker()
|
|
|
|
def focus_page_open(self):
|
|
|
|
"""
|
|
overloaded callback: the focus page is requested
|
|
"""
|
|
|
|
# stop unless dragging
|
|
if self.marker is None:
|
|
return
|
|
|
|
# configure the focus page and show it:
|
|
# the page will call the handler of "update_box" to commit
|
|
# changes to the box
|
|
fp = self.focus_page()
|
|
fp.box = self.box
|
|
fp.pfix = self.start_point
|
|
fp.update_box = self._update_box
|
|
ret = fp.show()
|
|
fp.update_box = None
|
|
if ret == 1:
|
|
# accepted: stop dragging now, we are done.
|
|
self._finish()
|
|
|
|
return ret
|
|
|
|
def activated(self):
|
|
|
|
"""
|
|
overloaded callback:
|
|
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):
|
|
|
|
"""
|
|
overloaded callback:
|
|
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):
|
|
|
|
"""
|
|
overloaded callback:
|
|
a mouse button was clicked
|
|
"""
|
|
|
|
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._update_box(pya.DBox(self.start_point, p))
|
|
self._finish()
|
|
return True
|
|
return False
|
|
|
|
def mouse_moved_event(self, p, buttons, prio):
|
|
|
|
"""
|
|
overloaded callback:
|
|
the mouse was moved
|
|
"""
|
|
|
|
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
|
|
|
|
class DragBoxPluginFactory(pya.PluginFactory):
|
|
|
|
"""
|
|
Implements a "plugin factory".
|
|
The purpose of this object is to create a plugin object
|
|
and corresponding UI objects.
|
|
"""
|
|
|
|
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):
|
|
"""
|
|
Called to create the configuration pages
|
|
"""
|
|
self.add_config_page(DragBoxConfigPage())
|
|
|
|
def create_editor_options_pages(self):
|
|
"""
|
|
Called to create the editor options pages
|
|
"""
|
|
self.add_editor_options_page(DragBoxEditorOptionsPage())
|
|
self.add_editor_options_page(DragBoxFocusPage())
|
|
|
|
def create_plugin(self, manager, root, view):
|
|
"""
|
|
Creates the plugin
|
|
"""
|
|
return DragBoxPlugin(view)
|
|
|
|
# Creates the singleton instance - as we register it,
|
|
# it is not garbage collected
|
|
DragBoxPluginFactory()
|
|
</text>
|
|
</klayout-macro>
|