mirror of https://github.com/KLayout/klayout.git
2158 lines
64 KiB
C++
2158 lines
64 KiB
C++
|
|
/*
|
|
|
|
KLayout Layout Viewer
|
|
Copyright (C) 2006-2017 Matthias Koefferlein
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
|
|
#include "dbUserObject.h"
|
|
#include "dbClipboard.h"
|
|
#include "tlString.h"
|
|
#include "tlAssert.h"
|
|
#include "layPlugin.h"
|
|
#include "layRenderer.h"
|
|
#include "laySnap.h"
|
|
#include "layLayoutView.h"
|
|
#include "laybasicConfig.h"
|
|
#include "layConverters.h"
|
|
#include "layLayoutCanvas.h"
|
|
#include "layProperties.h"
|
|
#include "antService.h"
|
|
#include "antPropertiesPage.h"
|
|
#include "antConfig.h"
|
|
|
|
namespace ant
|
|
{
|
|
|
|
// -------------------------------------------------------------
|
|
// Convert buttons to an angle constraint
|
|
|
|
static lay::angle_constraint_type
|
|
ac_from_buttons (unsigned int buttons)
|
|
{
|
|
if ((buttons & lay::ShiftButton) != 0) {
|
|
if ((buttons & lay::ControlButton) != 0) {
|
|
return lay::AC_Any;
|
|
} else {
|
|
return lay::AC_Ortho;
|
|
}
|
|
} else {
|
|
if ((buttons & lay::ControlButton) != 0) {
|
|
return lay::AC_Diagonal;
|
|
} else {
|
|
return lay::AC_Global;
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// Functionality to draw a ruler object
|
|
|
|
static void
|
|
tick_spacings (double d, double min_d, int &minor_ticks, double &ticks)
|
|
{
|
|
if (min_d > d) {
|
|
|
|
minor_ticks = -1;
|
|
ticks = -1;
|
|
|
|
} else {
|
|
|
|
const double log10 = log (10.0);
|
|
|
|
// as a safety measure, not too many ticks are created.
|
|
min_d = std::max (min_d, 0.001 * d);
|
|
|
|
double l1 = log (min_d) / log10;
|
|
double l0 = floor (l1);
|
|
l1 -= l0;
|
|
|
|
if (l1 < 0.3) {
|
|
minor_ticks = 5;
|
|
} else if (l1 < 0.7) {
|
|
minor_ticks = 2;
|
|
} else {
|
|
minor_ticks = 1;
|
|
}
|
|
|
|
ticks = exp (log10 * l0) * 10.0;
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Draws a ruler with the given parameters
|
|
*
|
|
* @param q1 The first point in pixel space
|
|
* @param q2 The second point in pixel space
|
|
* @param length_u The ruler length in micron
|
|
* @param min_spc_u The minimum tick spacing in micron
|
|
* @param sel True to draw ruler in "selected" mode
|
|
* @param right True to draw the ruler with ticks to the right (as seem from p1 to p2 in transformed space)
|
|
* @param style The style with which to draw the ruler
|
|
* @param pos The position where to draw the text
|
|
* @param bitmap The bitmap to draw the ruler on
|
|
* @param renderer The renderer object
|
|
*/
|
|
void
|
|
draw_ruler (const db::DPoint &q1,
|
|
const db::DPoint &q2,
|
|
double length_u,
|
|
double min_spc_u,
|
|
bool sel,
|
|
bool right,
|
|
ant::Object::style_type style,
|
|
lay::CanvasPlane *bitmap,
|
|
lay::Renderer &renderer)
|
|
{
|
|
double arrow_width = 8 / renderer.resolution ();
|
|
double arrow_length = 12 / renderer.resolution ();
|
|
double sel_width = 2 / renderer.resolution ();
|
|
|
|
if (length_u < 1e-5 /*micron*/ && style != ant::Object::STY_cross_both && style != ant::Object::STY_cross_end && style != ant::Object::STY_cross_start) {
|
|
|
|
if (sel) {
|
|
|
|
db::DBox b (q1 - db::DVector (sel_width * 0.5, sel_width * 0.5),
|
|
q2 + db::DVector (sel_width * 0.5, sel_width * 0.5));
|
|
|
|
renderer.draw (b, bitmap, bitmap, 0, 0);
|
|
|
|
} else {
|
|
renderer.draw (db::DEdge (q1, q1), 0, bitmap, 0, 0);
|
|
}
|
|
|
|
} else {
|
|
|
|
// compute the tick distribution
|
|
double tick_length = (style == ant::Object::STY_ruler ? 8 : 0) / renderer.resolution ();
|
|
|
|
double ticks = -1.0;
|
|
int minor_ticks = -1;
|
|
|
|
if (tick_length > 0) {
|
|
tick_spacings (length_u, min_spc_u, minor_ticks, ticks);
|
|
}
|
|
|
|
// normal and unit vector
|
|
|
|
double len = q1.double_distance (q2);
|
|
if ((style == ant::Object::STY_arrow_end || style == ant::Object::STY_arrow_start) && len < double (arrow_length) * 1.2) {
|
|
arrow_length = len / 1.2;
|
|
arrow_width = arrow_length * 2.0 / 3.0;
|
|
} else if (style == ant::Object::STY_arrow_both && len < double (arrow_length) * 2.4) {
|
|
arrow_length = len / 2.4;
|
|
arrow_width = arrow_length * 2.0 / 3.0;
|
|
}
|
|
|
|
db::DVector qq (q2.y () - q1.y (), q1.x () - q2.x ());
|
|
if (len > 1e-10) {
|
|
qq *= 1.0 / len;
|
|
} else {
|
|
qq = db::DVector (0.0, 1.0);
|
|
}
|
|
if (!right) {
|
|
qq = -qq;
|
|
}
|
|
|
|
db::DVector qu = q2 - q1;
|
|
if (len > 1e-10) {
|
|
qu *= 1.0 / len;
|
|
} else {
|
|
qu = db::DVector (1.0, 0.0);
|
|
}
|
|
|
|
// produce polygon stuff
|
|
|
|
if (sel && style != ant::Object::STY_none) {
|
|
|
|
db::DVector qw = qq * (double (sel_width) * 0.5);
|
|
|
|
db::DVector dq1, dq2;
|
|
if (style == ant::Object::STY_arrow_both || style == ant::Object::STY_arrow_start) {
|
|
dq1 = qu * double (arrow_length - 1);
|
|
}
|
|
if (style == ant::Object::STY_arrow_both || style == ant::Object::STY_arrow_end) {
|
|
dq2 = qu * double (-(arrow_length - 1));
|
|
}
|
|
|
|
db::DPolygon p;
|
|
db::DPoint points[] = {
|
|
db::DPoint (q1 + dq1 + qw),
|
|
db::DPoint (q2 + dq2 + qw),
|
|
db::DPoint (q2 + dq2 - qw),
|
|
db::DPoint (q1 + dq1 - qw),
|
|
};
|
|
p.assign_hull (points, points + sizeof (points) / sizeof (points [0]));
|
|
renderer.draw (p, bitmap, bitmap, 0, 0);
|
|
|
|
}
|
|
|
|
if (style == ant::Object::STY_arrow_end || style == ant::Object::STY_arrow_both) {
|
|
|
|
db::DPolygon p;
|
|
db::DPoint points[] = {
|
|
db::DPoint (q2),
|
|
db::DPoint (q2 + qq * double (arrow_width * 0.5) - qu * double (arrow_length)),
|
|
db::DPoint (q2 - qq * double (arrow_width * 0.5) - qu * double (arrow_length)),
|
|
};
|
|
p.assign_hull (points, points + sizeof (points) / sizeof (points [0]));
|
|
renderer.draw (p, bitmap, bitmap, 0, 0);
|
|
|
|
} else if (style == ant::Object::STY_cross_end || style == ant::Object::STY_cross_both) {
|
|
|
|
db::DPolygon p;
|
|
db::DPoint points[] = {
|
|
db::DPoint (q2),
|
|
db::DPoint (q2 + qq * double (arrow_width)),
|
|
db::DPoint (q2 - qq * double (arrow_width)),
|
|
db::DPoint (q2),
|
|
db::DPoint (q2 + qu * double (arrow_width)),
|
|
db::DPoint (q2 - qu * double (arrow_width)),
|
|
};
|
|
p.assign_hull (points, points + sizeof (points) / sizeof (points [0]), false /*don't compress*/);
|
|
renderer.draw (p, bitmap, bitmap, 0, 0);
|
|
|
|
}
|
|
|
|
if (style == ant::Object::STY_arrow_start || style == ant::Object::STY_arrow_both) {
|
|
|
|
db::DPolygon p;
|
|
db::DPoint points[] = {
|
|
db::DPoint (q1),
|
|
db::DPoint (q1 + qq * double (arrow_width * 0.5) + qu * double (arrow_length)),
|
|
db::DPoint (q1 - qq * double (arrow_width * 0.5) + qu * double (arrow_length))
|
|
};
|
|
p.assign_hull (points, points + sizeof (points) / sizeof (points [0]));
|
|
renderer.draw (p, bitmap, bitmap, 0, 0);
|
|
|
|
} else if (style == ant::Object::STY_cross_start || style == ant::Object::STY_cross_both) {
|
|
|
|
db::DPolygon p;
|
|
db::DPoint points[] = {
|
|
db::DPoint (q1),
|
|
db::DPoint (q1 + qq * double (arrow_width)),
|
|
db::DPoint (q1 - qq * double (arrow_width)),
|
|
db::DPoint (q1),
|
|
db::DPoint (q1 + qu * double (arrow_width)),
|
|
db::DPoint (q1 - qu * double (arrow_width)),
|
|
};
|
|
p.assign_hull (points, points + sizeof (points) / sizeof (points [0]), false /*don't compress*/);
|
|
renderer.draw (p, bitmap, bitmap, 0, 0);
|
|
|
|
}
|
|
|
|
// produce edge and text stuff
|
|
|
|
if (! sel && style != ant::Object::STY_none) {
|
|
renderer.draw (db::DEdge (q1, q2), 0, bitmap, 0, 0);
|
|
}
|
|
|
|
// create three tick vectors in tv_text, tv_short and tv_long
|
|
|
|
double tf = tick_length;
|
|
db::DVector tv_short = qq * tf * 0.5;
|
|
db::DVector tv_long = qq * tf;
|
|
|
|
if (tick_length > 0) {
|
|
renderer.draw (db::DEdge (q1, q1 + tv_long), 0, bitmap, 0, 0);
|
|
renderer.draw (db::DEdge (q2, q2 + tv_long), 0, bitmap, 0, 0);
|
|
}
|
|
|
|
if (minor_ticks > 0 && ticks > 0.0) {
|
|
|
|
db::DVector q = q2 - q1;
|
|
|
|
int nmax = int (floor ((length_u / ticks) * double (minor_ticks) - 1e-6));
|
|
for (int n = 1; n <= nmax; ++n) {
|
|
|
|
double r = ticks * double (n) / double (minor_ticks) / length_u;
|
|
|
|
db::DPoint qq = q1 + q * r;
|
|
qq = db::DPoint (floor (qq.x () + 0.5), floor (qq.y () + 0.5));
|
|
|
|
if (n % minor_ticks == 0) {
|
|
renderer.draw (db::DEdge (qq, qq + tv_long), 0, bitmap, 0, 0);
|
|
} else {
|
|
renderer.draw (db::DEdge (qq, qq + tv_short), 0, bitmap, 0, 0);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Draws a text with the given parameters
|
|
*
|
|
* @param q1 The first point in pixel space
|
|
* @param q2 The second point in pixel space
|
|
* @param length_u The ruler length in micron
|
|
* @param label The label text to draw
|
|
* @param right True to draw the ruler with ticks to the right (as seem from p1 to p2 in transformed space)
|
|
* @param style The style with which to draw the ruler
|
|
* @param pos The position where to draw the text
|
|
* @param halign The text's horizonal alignment
|
|
* @param valign The text's vertical alignment
|
|
* @param bitmap The bitmap to draw the ruler on
|
|
* @param renderer The renderer object
|
|
*/
|
|
void
|
|
draw_text (const db::DPoint &q1,
|
|
const db::DPoint &q2,
|
|
double length_u,
|
|
const std::string &label,
|
|
bool right,
|
|
ant::Object::style_type style,
|
|
ant::Object::position_type pos,
|
|
ant::Object::alignment_type halign,
|
|
ant::Object::alignment_type valign,
|
|
lay::CanvasPlane *bitmap,
|
|
lay::Renderer &renderer)
|
|
{
|
|
if (label.empty ()) {
|
|
return;
|
|
}
|
|
|
|
double arrow_width = 8 / renderer.resolution ();
|
|
double arrow_length = 12 / renderer.resolution ();
|
|
|
|
// Currently, "auto" means p2.
|
|
if (pos == ant::Object::POS_auto) {
|
|
pos = ant::Object::POS_p2;
|
|
}
|
|
|
|
if (length_u < 1e-5 /*micron*/ && style != ant::Object::STY_cross_both && style != ant::Object::STY_cross_end && style != ant::Object::STY_cross_start) {
|
|
|
|
renderer.draw (db::DBox (q1, q1),
|
|
label,
|
|
db::DefaultFont,
|
|
db::HAlignLeft,
|
|
db::VAlignBottom,
|
|
db::DFTrans (db::DFTrans::r0), 0, 0, 0, bitmap);
|
|
|
|
} else {
|
|
|
|
// compute the tick distribution
|
|
double tick_length = (style == ant::Object::STY_ruler ? 8 : 0) / renderer.resolution ();
|
|
|
|
// normal and unit vector
|
|
|
|
double len = q1.double_distance (q2);
|
|
if ((style == ant::Object::STY_arrow_end || style == ant::Object::STY_arrow_start) && len < double (arrow_length) * 1.2) {
|
|
arrow_length = len / 1.2;
|
|
arrow_width = arrow_length * 2.0 / 3.0;
|
|
} else if (style == ant::Object::STY_arrow_both && len < double (arrow_length) * 2.4) {
|
|
arrow_length = len / 2.4;
|
|
arrow_width = arrow_length * 2.0 / 3.0;
|
|
}
|
|
|
|
db::DVector qq (q2.y () - q1.y (), q1.x () - q2.x ());
|
|
if (len > 1e-10) {
|
|
qq *= 1.0 / len;
|
|
} else {
|
|
qq = db::DVector (0.0, 1.0);
|
|
}
|
|
if (!right) {
|
|
qq = -qq;
|
|
}
|
|
|
|
db::DVector qu = q2 - q1;
|
|
if (len > 1e-10) {
|
|
qu *= 1.0 / len;
|
|
} else {
|
|
qu = db::DVector (1.0, 0.0);
|
|
}
|
|
|
|
db::HAlign text_halign = db::HAlignCenter;
|
|
if (halign == ant::Object::AL_auto) {
|
|
// Compute a nice alignment depending on the anchor point
|
|
if (fabs (qq.x ()) > 1e-6) {
|
|
text_halign = qq.x () > 0.0 ? db::HAlignLeft : db::HAlignRight;
|
|
} else if (length_u < 1e-5) {
|
|
text_halign = db::HAlignLeft;
|
|
} else if (pos == ant::Object::POS_p2) {
|
|
text_halign = q2.x () < q1.x () ? db::HAlignLeft : db::HAlignRight;
|
|
} else if (pos == ant::Object::POS_p1) {
|
|
text_halign = q1.x () < q2.x () ? db::HAlignLeft : db::HAlignRight;
|
|
} else {
|
|
text_halign = db::HAlignCenter;
|
|
}
|
|
} else if (halign == ant::Object::AL_left) {
|
|
text_halign = db::HAlignLeft;
|
|
} else if (halign == ant::Object::AL_right) {
|
|
text_halign = db::HAlignRight;
|
|
}
|
|
|
|
db::VAlign text_valign = db::VAlignCenter;
|
|
if (valign == ant::Object::AL_auto) {
|
|
// Compute a nice alignment depending on the anchor point
|
|
if (length_u < 1e-5) {
|
|
text_valign = db::VAlignBottom;
|
|
} else if (fabs (qq.y ()) > 1e-6) {
|
|
text_valign = qq.y () > 0.0 ? db::VAlignBottom : db::VAlignTop;
|
|
} else if (pos == ant::Object::POS_p2) {
|
|
text_valign = q1.y () > q2.y () ? db::VAlignBottom : db::VAlignTop;
|
|
} else if (pos == ant::Object::POS_p1) {
|
|
text_valign = q2.y () > q1.y () ? db::VAlignBottom : db::VAlignTop;
|
|
} else {
|
|
text_valign = db::VAlignCenter;
|
|
}
|
|
} else if (valign == ant::Object::AL_bottom) {
|
|
text_valign = db::VAlignBottom;
|
|
} else if (valign == ant::Object::AL_top) {
|
|
text_valign = db::VAlignTop;
|
|
}
|
|
|
|
db::DVector tv_text;
|
|
if (style == ant::Object::STY_arrow_start || style == ant::Object::STY_arrow_both || style == ant::Object::STY_arrow_end) {
|
|
tv_text = qq * (arrow_width * 0.5 + 2.0);
|
|
} else if (style == ant::Object::STY_cross_start || style == ant::Object::STY_cross_both || style == ant::Object::STY_cross_end) {
|
|
if (length_u < 1e-5 /*micron*/) {
|
|
if (text_halign == db::HAlignRight) {
|
|
tv_text = (qq - qu) * 2.0;
|
|
} else if (text_halign == db::HAlignLeft) {
|
|
tv_text = (qq + qu) * 2.0;
|
|
} else {
|
|
tv_text = qq * 2.0;
|
|
}
|
|
} else {
|
|
tv_text = qq * (arrow_width + 2.0);
|
|
}
|
|
} else {
|
|
tv_text = qq * (tick_length + 2.0);
|
|
}
|
|
|
|
if (text_halign == db::HAlignCenter) {
|
|
tv_text.set_x (0);
|
|
} else if (text_halign == db::HAlignRight) {
|
|
tv_text.set_x (std::min (tv_text.x (), 0.0));
|
|
} else if (text_halign == db::HAlignLeft){
|
|
tv_text.set_x (std::max (tv_text.x (), 0.0));
|
|
}
|
|
|
|
if (text_valign == db::VAlignCenter) {
|
|
tv_text.set_y (0);
|
|
} else if (text_valign == db::VAlignTop) {
|
|
tv_text.set_y (std::min (tv_text.y (), 0.0));
|
|
} else if (text_valign == db::VAlignBottom){
|
|
tv_text.set_y (std::max (tv_text.y (), 0.0));
|
|
}
|
|
|
|
db::DPoint tp = q2;
|
|
if (pos == ant::Object::POS_center) {
|
|
tp = q1 + (q2 - q1) * 0.5;
|
|
} else if (pos == ant::Object::POS_p1) {
|
|
tp = q1;
|
|
}
|
|
|
|
renderer.draw (db::DBox (tp + tv_text, tp + tv_text),
|
|
label,
|
|
db::DefaultFont,
|
|
text_halign,
|
|
text_valign,
|
|
db::DFTrans (db::DFTrans::r0), 0, 0, 0, bitmap);
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Draws an ellipse with the given parameters
|
|
*
|
|
* @param q1 The first point in pixel space
|
|
* @param q2 The second point in pixel space
|
|
* @param sel True to draw ruler in "selected" mode
|
|
* @param bitmap The bitmap to draw the ruler on
|
|
* @param renderer The renderer object
|
|
*/
|
|
void
|
|
draw_ellipse (const db::DPoint &q1,
|
|
const db::DPoint &q2,
|
|
double length_u,
|
|
bool sel,
|
|
lay::CanvasPlane *bitmap,
|
|
lay::Renderer &renderer)
|
|
{
|
|
double sel_width = 2 / renderer.resolution ();
|
|
|
|
if (length_u < 1e-5 /*micron*/) {
|
|
|
|
if (sel) {
|
|
|
|
db::DBox b (q1 - db::DVector (sel_width * 0.5, sel_width * 0.5),
|
|
q2 + db::DVector (sel_width * 0.5, sel_width * 0.5));
|
|
|
|
renderer.draw (b, bitmap, bitmap, 0, 0);
|
|
|
|
} else {
|
|
renderer.draw (db::DEdge (q1, q1), 0, bitmap, 0, 0);
|
|
}
|
|
|
|
} else {
|
|
|
|
int npoints = 200;
|
|
|
|
// produce polygon stuff
|
|
|
|
double rx = fabs ((q2 - q1).x () * 0.5);
|
|
double ry = fabs ((q2 - q1).y () * 0.5);
|
|
db::DPoint c = q1 + (q2 - q1) * 0.5;
|
|
|
|
db::DPolygon p;
|
|
|
|
std::vector<db::DPoint> pts;
|
|
pts.reserve (npoints);
|
|
|
|
if (sel) {
|
|
rx += sel_width * 0.5;
|
|
ry += sel_width * 0.5;
|
|
}
|
|
|
|
double da = M_PI * 2.0 / double (npoints);
|
|
for (int i = 0; i < npoints; ++i) {
|
|
double a = da * i;
|
|
pts.push_back (c + db::DVector (rx * cos (a), ry * sin (a)));
|
|
}
|
|
|
|
p.assign_hull (pts.begin (), pts.end ());
|
|
|
|
if (sel) {
|
|
|
|
pts.clear ();
|
|
|
|
rx -= sel_width;
|
|
ry -= sel_width;
|
|
for (int i = 0; i < npoints; ++i) {
|
|
double a = da * i;
|
|
pts.push_back (c + db::DVector (rx * cos (a), ry * sin (a)));
|
|
}
|
|
|
|
p.insert_hole (pts.begin (), pts.end ());
|
|
|
|
renderer.draw (p, bitmap, bitmap, 0, 0);
|
|
|
|
} else {
|
|
|
|
for (db::DPolygon::polygon_edge_iterator e = p.begin_edge (); ! e.at_end (); ++e) {
|
|
renderer.draw (*e, 0, bitmap, 0, 0);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
draw_ruler (const ant::Object &ruler, const db::DCplxTrans &trans, bool sel, lay::CanvasPlane *bitmap, lay::Renderer &renderer)
|
|
{
|
|
// round the starting point, shift both, and round the end point
|
|
std::pair <db::DPoint, db::DPoint> v = lay::snap (trans * ruler.p1 (), trans * ruler.p2 ());
|
|
db::DPoint q1 = v.first;
|
|
db::DPoint q2 = v.second;
|
|
|
|
bool xy_swapped = ((trans.rot () % 2) != 0);
|
|
double lu = ruler.p1 ().double_distance (ruler.p2 ());
|
|
int min_tick_spc = int (0.5 + 20 / renderer.resolution ()); // min tick spacing in canvas units
|
|
double mu = double (min_tick_spc) / trans.ctrans (1.0);
|
|
|
|
if (ruler.outline () == Object::OL_diag) {
|
|
draw_ruler (q1, q2, lu, mu, sel, q2.x () < q1.x (), ruler.style (), bitmap, renderer);
|
|
draw_text (q1, q2, lu, ruler.text (), q2.x () < q1.x (), ruler.style (), ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer);
|
|
}
|
|
|
|
if ((!xy_swapped && (ruler.outline () == Object::OL_xy || ruler.outline () == Object::OL_diag_xy)) ||
|
|
( xy_swapped && (ruler.outline () == Object::OL_yx || ruler.outline () == Object::OL_diag_yx))) {
|
|
|
|
bool r = (q2.x () > q1.x ()) ^ (q2.y () < q1.y ());
|
|
|
|
if (ruler.outline () == Object::OL_diag_xy || ruler.outline () == Object::OL_diag_yx) {
|
|
draw_ruler (q1, q2, lu, mu, sel, !r, ruler.style (), bitmap, renderer);
|
|
draw_text (q1, q2, lu, ruler.text (), !r, ruler.style (), ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer);
|
|
}
|
|
draw_ruler (q1, db::DPoint (q2.x (), q1.y ()), lu, mu, sel, r, ruler.style (), bitmap, renderer);
|
|
draw_text (q1, db::DPoint (q2.x (), q1.y ()), lu, ruler.text_x (trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer);
|
|
draw_ruler (db::DPoint (q2.x (), q1.y ()), q2, lu, mu, sel, r, ruler.style (), bitmap, renderer);
|
|
draw_text (db::DPoint (q2.x (), q1.y ()), q2, lu, ruler.text_y (trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer);
|
|
|
|
}
|
|
|
|
if ((!xy_swapped && (ruler.outline () == Object::OL_yx || ruler.outline () == Object::OL_diag_yx)) ||
|
|
( xy_swapped && (ruler.outline () == Object::OL_xy || ruler.outline () == Object::OL_diag_xy))) {
|
|
|
|
bool r = (q2.x () > q1.x ()) ^ (q2.y () > q1.y ());
|
|
|
|
if (ruler.outline () == Object::OL_diag_xy || ruler.outline () == Object::OL_diag_yx) {
|
|
draw_ruler (q1, q2, lu, mu, sel, !r, ruler.style (), bitmap, renderer);
|
|
draw_text (q1, q2, lu, ruler.text (), !r, ruler.style (), ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer);
|
|
}
|
|
draw_ruler (q1, db::DPoint (q1.x (), q2.y ()), lu, mu, sel, r, ruler.style (), bitmap, renderer);
|
|
draw_text (q1, db::DPoint (q1.x (), q2.y ()), lu, ruler.text_y (trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer);
|
|
draw_ruler (db::DPoint (q1.x (), q2.y ()), q2, lu, mu, sel, r, ruler.style (), bitmap, renderer);
|
|
draw_text (db::DPoint (q1.x (), q2.y ()), q2, lu, ruler.text_x (trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer);
|
|
|
|
}
|
|
|
|
if (ruler.outline () == Object::OL_box) {
|
|
|
|
bool r = (q2.x () > q1.x ()) ^ (q2.y () < q1.y ());
|
|
|
|
draw_ruler (q1, db::DPoint (q2.x (), q1.y ()), lu, mu, sel, r, ruler.style (), bitmap, renderer);
|
|
draw_text (q1, db::DPoint (q2.x (), q1.y ()), lu, ruler.text_x (trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer);
|
|
draw_ruler (db::DPoint (q2.x (), q1.y ()), q2, lu, mu, sel, r, ruler.style (), bitmap, renderer);
|
|
draw_text (db::DPoint (q2.x (), q1.y ()), q2, lu, ruler.text_y (trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer);
|
|
draw_ruler (q1, db::DPoint (q1.x (), q2.y ()), lu, mu, sel, !r, ruler.style (), bitmap, renderer);
|
|
draw_ruler (db::DPoint (q1.x (), q2.y ()), q2, lu, mu, sel, !r, ruler.style (), bitmap, renderer);
|
|
draw_text (q1, q2, lu, ruler.text (), !r, ant::Object::STY_none, ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer);
|
|
|
|
} else if (ruler.outline () == Object::OL_ellipse) {
|
|
|
|
bool r = (q2.x () > q1.x ()) ^ (q2.y () < q1.y ());
|
|
|
|
draw_text (q1, db::DPoint (q2.x (), q1.y ()), lu, ruler.text_x (trans.fp_trans ()), r, ant::Object::STY_none, ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer);
|
|
draw_text (db::DPoint (q2.x (), q1.y ()), q2, lu, ruler.text_y (trans.fp_trans ()), r, ant::Object::STY_none, ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer);
|
|
draw_text (q1, q2, lu, ruler.text (), !r, ant::Object::STY_none, ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer);
|
|
|
|
draw_ellipse (q1, q2, lu, sel, bitmap, renderer);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------
|
|
|
|
// search range for select in pixels
|
|
static unsigned int search_range = 5; // TODO: make variable?
|
|
|
|
// -------------------------------------------------------------
|
|
|
|
View::View (ant::Service *rulers, const ant::Object *ruler, bool selected)
|
|
: lay::ViewObject (rulers->widget ()),
|
|
mp_rulers (rulers), m_selected (selected), mp_ruler (ruler)
|
|
{
|
|
// .. nothing else ..
|
|
}
|
|
|
|
View::~View ()
|
|
{
|
|
// .. nothing else ..
|
|
}
|
|
|
|
void
|
|
View::transform_by (const db::DCplxTrans &t)
|
|
{
|
|
if (m_trans != t) {
|
|
m_trans = t;
|
|
redraw ();
|
|
}
|
|
}
|
|
|
|
void
|
|
View::ruler (const ant::Object *r)
|
|
{
|
|
mp_ruler = r;
|
|
redraw ();
|
|
}
|
|
|
|
void
|
|
View::render (const lay::Viewport &vp, lay::ViewObjectCanvas &canvas)
|
|
{
|
|
if (! mp_ruler) {
|
|
return;
|
|
}
|
|
|
|
int basic_width = int(0.5 + 1.0 / canvas.resolution ());
|
|
|
|
QColor c (mp_rulers->color ());
|
|
if (! c.isValid ()) {
|
|
c = QColor (canvas.foreground_color ().rgb ());
|
|
}
|
|
|
|
// obtain bitmap to render on
|
|
lay::CanvasPlane *plane;
|
|
if (mp_rulers->with_halo ()) {
|
|
std::vector <lay::ViewOp> ops;
|
|
ops.reserve (2);
|
|
// we use 2 and 3 for the bitmap index. Since selection markers are using 0 and 1, rulers
|
|
// that are dragged appear in front of them.
|
|
ops.push_back (lay::ViewOp (canvas.background_color ().rgb (), lay::ViewOp::Copy, 0, 0, 0, lay::ViewOp::Rect, 3 * basic_width, 2));
|
|
ops.push_back (lay::ViewOp (c.rgb (), lay::ViewOp::Copy, 0, 0, 0, lay::ViewOp::Rect, basic_width, 3));
|
|
plane = canvas.plane (ops);
|
|
} else {
|
|
plane = canvas.plane (lay::ViewOp (c.rgb (), lay::ViewOp::Copy, 0, 0, 0, lay::ViewOp::Rect, basic_width));
|
|
}
|
|
|
|
draw_ruler (*mp_ruler, vp.trans () * m_trans, m_selected, plane, canvas.renderer ());
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// ant::Service implementation
|
|
|
|
Service::Service (db::Manager *manager, lay::LayoutView *view)
|
|
: lay::ViewService (view->view_object_widget ()),
|
|
lay::Editable (view),
|
|
lay::Plugin (view),
|
|
lay::Drawing (1/*number of planes*/, view->drawings ()),
|
|
db::Object (manager),
|
|
m_halo (true),
|
|
m_snap_mode (lay::AC_Any),
|
|
m_grid (0.001),
|
|
m_grid_snap (false), m_obj_snap (false), m_snap_range (1),
|
|
m_max_number_of_rulers (-1 /*unlimited*/),
|
|
mp_view (view),
|
|
mp_active_ruler (0),
|
|
mp_transient_ruler (0),
|
|
m_drawing (false), m_current (),
|
|
m_move_mode (MoveNone),
|
|
m_current_template (0)
|
|
{
|
|
mp_view->annotations_changed_event.add (this, &Service::annotations_changed);
|
|
}
|
|
|
|
Service::~Service ()
|
|
{
|
|
for (std::vector<ant::View *>::iterator r = m_rulers.begin (); r != m_rulers.end (); ++r) {
|
|
delete *r;
|
|
}
|
|
m_rulers.clear ();
|
|
clear_transient_selection ();
|
|
}
|
|
|
|
bool
|
|
Service::configure (const std::string &name, const std::string &value)
|
|
{
|
|
bool taken = true;
|
|
|
|
if (name == cfg_ruler_color) {
|
|
|
|
QColor color;
|
|
lay::ColorConverter ().from_string (value, color);
|
|
|
|
// make the color available for the dynamic view objects too.
|
|
if (lay::test_and_set (m_color, color)) {
|
|
widget ()->touch ();
|
|
}
|
|
|
|
} else if (name == cfg_ruler_halo) {
|
|
|
|
bool halo;
|
|
tl::from_string (value, halo);
|
|
|
|
// make the color available for the dynamic view objects too.
|
|
if (lay::test_and_set (m_halo, halo)) {
|
|
widget ()->touch ();
|
|
}
|
|
|
|
} else if (name == cfg_ruler_grid_micron) {
|
|
|
|
double g = 0;
|
|
tl::from_string (value, g);
|
|
m_grid = g;
|
|
taken = false; // to let others use the grid too.
|
|
|
|
} else if (name == cfg_max_number_of_rulers) {
|
|
|
|
int n = -1;
|
|
tl::from_string (value, n);
|
|
if (n != m_max_number_of_rulers) {
|
|
m_max_number_of_rulers = n;
|
|
reduce_rulers (n);
|
|
}
|
|
|
|
} else if (name == cfg_ruler_snap_range) {
|
|
|
|
int n = 0;
|
|
tl::from_string (value, n);
|
|
m_snap_range = n;
|
|
|
|
} else if (name == cfg_ruler_obj_snap) {
|
|
tl::from_string (value, m_obj_snap);
|
|
} else if (name == cfg_ruler_grid_snap) {
|
|
tl::from_string (value, m_grid_snap);
|
|
} else if (name == cfg_ruler_snap_mode) {
|
|
|
|
lay::angle_constraint_type sm = lay::AC_Any;
|
|
ACConverter ().from_string (value, sm);
|
|
m_snap_mode = sm;
|
|
|
|
} else if (name == cfg_ruler_templates) {
|
|
m_ruler_templates = ant::Template::from_string (value);
|
|
} else if (name == cfg_current_ruler_template) {
|
|
|
|
int n = 0;
|
|
tl::from_string (value, n);
|
|
m_current_template = n;
|
|
|
|
} else {
|
|
taken = false;
|
|
}
|
|
|
|
return taken;
|
|
|
|
}
|
|
|
|
const ant::Template &
|
|
Service::current_template () const
|
|
{
|
|
if (m_current_template >= m_ruler_templates.size ()) {
|
|
static ant::Template s_def_template;
|
|
return s_def_template;
|
|
} else {
|
|
return m_ruler_templates [m_current_template];
|
|
}
|
|
}
|
|
|
|
void
|
|
Service::config_finalize ()
|
|
{
|
|
}
|
|
|
|
void
|
|
Service::annotations_changed ()
|
|
{
|
|
// NOTE: right now, we don't differentiate: every annotation change may be a change in an image too.
|
|
// We just forward this event as a potential image changed event
|
|
annotations_changed_event ();
|
|
}
|
|
|
|
std::vector <lay::ViewOp>
|
|
Service::get_view_ops (lay::RedrawThreadCanvas &canvas, QColor background, QColor foreground, QColor /*active*/) const
|
|
{
|
|
int basic_width = int(0.5 + 1.0 / canvas.resolution ());
|
|
|
|
// the changing of the view ops is done here since it may depend on the
|
|
// background color which might be changed by another configure call later.
|
|
std::vector <lay::ViewOp> view_ops;
|
|
if (m_halo) {
|
|
view_ops.push_back (lay::ViewOp (background.rgb (), lay::ViewOp::Copy, 0, 0, 0, lay::ViewOp::Rect, 3 * basic_width, 0));
|
|
}
|
|
if (m_color.isValid ()) {
|
|
view_ops.push_back (lay::ViewOp (m_color.rgb (), lay::ViewOp::Copy, 0, 0, 0, lay::ViewOp::Rect, basic_width, 0));
|
|
} else {
|
|
view_ops.push_back (lay::ViewOp (foreground.rgb (), lay::ViewOp::Copy, 0, 0, 0, lay::ViewOp::Rect, basic_width, 0));
|
|
}
|
|
|
|
return view_ops;
|
|
}
|
|
|
|
void
|
|
Service::clear_highlights ()
|
|
{
|
|
for (std::vector<ant::View *>::iterator r = m_rulers.begin (); r != m_rulers.end (); ++r) {
|
|
(*r)->visible (false);
|
|
}
|
|
}
|
|
|
|
void
|
|
Service::restore_highlights ()
|
|
{
|
|
for (std::vector<ant::View *>::iterator r = m_rulers.begin (); r != m_rulers.end (); ++r) {
|
|
(*r)->visible (true);
|
|
}
|
|
}
|
|
|
|
void
|
|
Service::highlight (unsigned int n)
|
|
{
|
|
for (std::vector<ant::View *>::iterator r = m_rulers.begin (); r != m_rulers.end (); ++r) {
|
|
(*r)->visible (n-- == 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
Service::clear_rulers ()
|
|
{
|
|
drag_cancel ();
|
|
reduce_rulers (0);
|
|
}
|
|
|
|
void
|
|
Service::drag_cancel ()
|
|
{
|
|
if (m_drawing) {
|
|
|
|
widget ()->ungrab_mouse (this);
|
|
|
|
if (mp_active_ruler) {
|
|
delete mp_active_ruler;
|
|
mp_active_ruler = 0;
|
|
}
|
|
|
|
m_drawing = false;
|
|
|
|
}
|
|
}
|
|
|
|
int
|
|
Service::insert_ruler (const ant::Object &ruler, bool limit_number)
|
|
{
|
|
// determine the last id
|
|
int idmax = -1;
|
|
for (lay::AnnotationShapes::iterator r = mp_view->annotation_shapes ().begin (); r != mp_view->annotation_shapes ().end (); ++r) {
|
|
const ant::Object *robj = dynamic_cast <const ant::Object *> (r->ptr ());
|
|
if (robj) {
|
|
if (robj->id () > idmax) {
|
|
idmax = robj->id ();
|
|
}
|
|
}
|
|
}
|
|
|
|
// create the ruler from the template
|
|
ant::Object *new_ruler = new ant::Object (ruler);
|
|
new_ruler->id (idmax + 1);
|
|
mp_view->annotation_shapes ().insert (db::DUserObject (new_ruler));
|
|
|
|
// delete surplus rulers
|
|
if (limit_number) {
|
|
reduce_rulers (m_max_number_of_rulers);
|
|
}
|
|
|
|
return new_ruler->id ();
|
|
}
|
|
|
|
/**
|
|
* @brief Helper function to determine which move mode to choose given a certain search box and ant::Object
|
|
*/
|
|
static bool
|
|
dragging_what (const ant::Object *robj, const db::DBox &search_dbox, ant::Service::MoveMode &mode, db::DPoint &p1)
|
|
{
|
|
db::DPoint p12, p21;
|
|
bool has_p12 = false, has_p21 = false;
|
|
|
|
db::DPoint p11 = robj->p1 (), p22 = robj->p2 ();
|
|
db::DPoint c = p11 + (p22 - p11) * 0.5;
|
|
|
|
if (robj->outline () == ant::Object::OL_xy || robj->outline () == ant::Object::OL_diag_xy || robj->outline () == ant::Object::OL_box) {
|
|
p12 = db::DPoint (robj->p2 ().x (), robj->p1 ().y ());
|
|
has_p12 = true;
|
|
}
|
|
|
|
if (robj->outline () == ant::Object::OL_yx || robj->outline () == ant::Object::OL_diag_yx || robj->outline () == ant::Object::OL_box) {
|
|
p21 = db::DPoint (robj->p1 ().x (), robj->p2 ().y ());
|
|
has_p21 = true;
|
|
}
|
|
|
|
if (robj->outline () == ant::Object::OL_ellipse) {
|
|
db::DVector d = (p22 - p11) * 0.5;
|
|
p12 = c + db::DVector (d.x (), -d.y ());
|
|
p21 = c + db::DVector (-d.x (), d.y ());
|
|
has_p12 = true;
|
|
has_p21 = true;
|
|
}
|
|
|
|
// HINT: this was implemented returning a std::pair<MoveMode, db::DPoint>, but
|
|
// I was not able to get it to work in gcc 4.1.2 in -O3 mode ...
|
|
|
|
if (search_dbox.contains (p11)) {
|
|
p1 = p11;
|
|
mode = ant::Service::MoveP1;
|
|
return true;
|
|
}
|
|
if (search_dbox.contains (p22)) {
|
|
p1 = p22;
|
|
mode = ant::Service::MoveP2;
|
|
return true;
|
|
}
|
|
if (has_p12 && search_dbox.contains (p12)) {
|
|
p1 = p12;
|
|
mode = ant::Service::MoveP12;
|
|
return true;
|
|
}
|
|
if (has_p21 && search_dbox.contains (p21)) {
|
|
p1 = p21;
|
|
mode = ant::Service::MoveP21;
|
|
return true;
|
|
}
|
|
if (has_p12 && search_dbox.touches (db::DBox (p12, p22))) {
|
|
p1 = db::DPoint (p12.x (), search_dbox.center ().y ());
|
|
mode = ant::Service::MoveP2X;
|
|
return true;
|
|
}
|
|
if (has_p21 && search_dbox.touches (db::DBox (p21, p11))) {
|
|
p1 = db::DPoint (p21.x (), search_dbox.center ().y ());
|
|
mode = ant::Service::MoveP1X;
|
|
return true;
|
|
}
|
|
if (has_p12 && search_dbox.touches (db::DBox (p12, p11))) {
|
|
p1 = db::DPoint (search_dbox.center ().x (), p12.y ());
|
|
mode = ant::Service::MoveP1Y;
|
|
return true;
|
|
}
|
|
if (has_p21 && search_dbox.touches (db::DBox (p21, p22))) {
|
|
p1 = db::DPoint (search_dbox.center ().x (), p21.y ());
|
|
mode = ant::Service::MoveP2Y;
|
|
return true;
|
|
}
|
|
if ((robj->outline () == ant::Object::OL_diag || robj->outline () == ant::Object::OL_diag_xy || robj->outline () == ant::Object::OL_diag_yx)
|
|
&& db::DEdge (p11, p22).distance_abs (search_dbox.center ()) <= search_dbox.width () * 0.5) {
|
|
p1 = search_dbox.center ();
|
|
mode = ant::Service::MoveRuler;
|
|
return true;
|
|
}
|
|
if ((robj->outline () == ant::Object::OL_box || robj->outline () == ant::Object::OL_ellipse) && search_dbox.inside (db::DBox (p11, p22))) {
|
|
p1 = search_dbox.center ();
|
|
mode = ant::Service::MoveRuler;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
Service::begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::angle_constraint_type /*ac*/)
|
|
{
|
|
// cancel any pending move or drag operations, reset mp_active_ruler
|
|
widget ()->drag_cancel (); // KLUDGE: every service does this to the same service manager
|
|
|
|
// choose move mode
|
|
if (mode == lay::Editable::Selected) {
|
|
|
|
m_move_mode = MoveSelected;
|
|
m_p1 = p;
|
|
m_trans = db::DTrans (db::DPoint () - m_p1);
|
|
|
|
for (std::vector <ant::View *>::iterator r = m_rulers.begin (); r != m_rulers.end (); ++r) {
|
|
(*r)->thaw ();
|
|
}
|
|
return false;
|
|
|
|
} else if (mode == lay::Editable::Partial) {
|
|
|
|
m_move_mode = MoveNone;
|
|
|
|
// compute search box
|
|
double l = double (search_range) / widget ()->mouse_event_trans ().mag ();
|
|
db::DBox search_dbox = db::DBox (p, p).enlarged (db::DVector (l, l));
|
|
|
|
// test, wether we are moving a handle of one selected object
|
|
for (std::map<obj_iterator, unsigned int>::const_iterator r = m_selected.begin (); r != m_selected.end (); ++r) {
|
|
|
|
obj_iterator ri = r->first;
|
|
const ant::Object *robj = dynamic_cast <const ant::Object *> ((*ri).ptr ());
|
|
if (robj) {
|
|
|
|
if (dragging_what (robj, search_dbox, m_move_mode, m_p1) && m_move_mode != MoveRuler) {
|
|
|
|
// found anything: make the moved ruler the selection
|
|
clear_selection ();
|
|
m_selected.insert (std::make_pair (ri, 0));
|
|
m_current = *robj;
|
|
m_original = m_current;
|
|
m_rulers.push_back (new ant::View (this, &m_current, true));
|
|
m_rulers.back ()->thaw ();
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// nothing was found
|
|
return false;
|
|
|
|
} else if (mode == lay::Editable::Any) {
|
|
|
|
m_move_mode = MoveNone;
|
|
|
|
// compute search box
|
|
double l = double (search_range) / widget ()->mouse_event_trans ().mag ();
|
|
db::DBox search_dbox = db::DBox (p, p).enlarged (db::DVector (l, l));
|
|
|
|
// box-selection
|
|
|
|
lay::AnnotationShapes::touching_iterator r = mp_view->annotation_shapes ().begin_touching (search_dbox);
|
|
|
|
while (m_move_mode == MoveNone && ! r.at_end ()) {
|
|
|
|
const ant::Object *robj = dynamic_cast <const ant::Object *> ((*r).ptr ());
|
|
if (robj) {
|
|
|
|
if (dragging_what (robj, search_dbox, m_move_mode, m_p1)) {
|
|
|
|
// found anything: make the moved ruler the selection
|
|
clear_selection ();
|
|
m_selected.insert (std::make_pair (mp_view->annotation_shapes ().iterator_from_pointer (&*r), 0));
|
|
m_current = *robj;
|
|
m_original = m_current;
|
|
m_rulers.push_back (new ant::View (this, &m_current, true));
|
|
m_rulers.back ()->thaw ();
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
++r;
|
|
|
|
}
|
|
|
|
// nothing was found
|
|
return false;
|
|
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void
|
|
Service::move_transform (const db::DPoint &p, db::DFTrans tr, lay::angle_constraint_type /*ac*/)
|
|
{
|
|
if (m_rulers.empty () || m_selected.empty ()) {
|
|
return;
|
|
}
|
|
|
|
if (m_move_mode == MoveRuler) {
|
|
|
|
db::DVector dp = p - db::DPoint ();
|
|
|
|
m_original.transform (db::DTrans (m_p1 - db::DPoint ()) * db::DTrans (tr) * db::DTrans (db::DPoint () - m_p1));
|
|
m_current.transform (db::DTrans (dp) * db::DTrans (tr) * db::DTrans (-dp));
|
|
|
|
// display current rulers' parameters
|
|
show_message ();
|
|
|
|
m_rulers [0]->redraw ();
|
|
|
|
} else if (m_move_mode == MoveSelected) {
|
|
|
|
m_trans *= db::DTrans (m_p1 - db::DPoint ()) * db::DTrans (tr) * db::DTrans (db::DPoint () - m_p1);
|
|
|
|
for (std::vector<ant::View *>::iterator r = m_rulers.begin (); r != m_rulers.end (); ++r) {
|
|
(*r)->transform_by (db::DCplxTrans (m_trans));
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
Service::move (const db::DPoint &p, lay::angle_constraint_type ac)
|
|
{
|
|
if (m_rulers.empty () || m_selected.empty ()) {
|
|
return;
|
|
}
|
|
|
|
if (m_move_mode == MoveP1) {
|
|
|
|
m_current.p1 (snap2 (m_p1, p, &m_current, ac).second);
|
|
m_rulers [0]->redraw ();
|
|
|
|
} else if (m_move_mode == MoveP2) {
|
|
|
|
m_current.p2 (snap2 (m_p1, p, &m_current, ac).second);
|
|
m_rulers [0]->redraw ();
|
|
|
|
} else if (m_move_mode == MoveP12) {
|
|
|
|
db::DPoint p12 = snap2 (m_p1, p, &m_current, ac).second;
|
|
m_current.p1 (db::DPoint (m_current.p1 ().x(), p12.y ()));
|
|
m_current.p2 (db::DPoint (p12.x (), m_current.p2 ().y ()));
|
|
m_rulers [0]->redraw ();
|
|
|
|
} else if (m_move_mode == MoveP21) {
|
|
|
|
db::DPoint p21 = snap2 (m_p1, p, &m_current, ac).second;
|
|
m_current.p1 (db::DPoint (p21.x (), m_current.p1 ().y ()));
|
|
m_current.p2 (db::DPoint (m_current.p2 ().x(), p21.y ()));
|
|
m_rulers [0]->redraw ();
|
|
|
|
} else if (m_move_mode == MoveP1X) {
|
|
|
|
db::DPoint pc = snap2 (m_p1, p, &m_current, ac).second;
|
|
m_current.p1 (db::DPoint (pc.x (), m_current.p1 ().y ()));
|
|
m_rulers [0]->redraw ();
|
|
|
|
} else if (m_move_mode == MoveP2X) {
|
|
|
|
db::DPoint pc = snap2 (m_p1, p, &m_current, ac).second;
|
|
m_current.p2 (db::DPoint (pc.x (), m_current.p2 ().y ()));
|
|
m_rulers [0]->redraw ();
|
|
|
|
} else if (m_move_mode == MoveP1Y) {
|
|
|
|
db::DPoint pc = snap2 (m_p1, p, &m_current, ac).second;
|
|
m_current.p1 (db::DPoint (m_current.p1 ().x (), pc.y ()));
|
|
m_rulers [0]->redraw ();
|
|
|
|
} else if (m_move_mode == MoveP2Y) {
|
|
|
|
db::DPoint pc = snap2 (m_p1, p, &m_current, ac).second;
|
|
m_current.p2 (db::DPoint (m_current.p2 ().x (), pc.y ()));
|
|
m_rulers [0]->redraw ();
|
|
|
|
} else if (m_move_mode == MoveRuler) {
|
|
|
|
// try two ways of snapping
|
|
db::DVector dp = p - m_p1;
|
|
|
|
db::DPoint p1 = m_original.p1 () + dp;
|
|
db::DPoint p2 = m_original.p2 () + dp;
|
|
|
|
std::pair<bool, db::DPoint> r1 = snap1 (p1, m_obj_snap && m_original.snap ());
|
|
db::DPoint q1 = r1.second;
|
|
std::pair<bool, db::DPoint> r2 = snap1 (p2, m_obj_snap && m_original.snap ());
|
|
db::DPoint q2 = r2.second;
|
|
|
|
if ((!r2.first && r1.first) || ((r1.first || (!r1.first && !r2.first)) && q1.distance (p1) < q2.distance (p2))) {
|
|
q2 = q1 + (m_original.p2 () - m_original.p1 ());
|
|
} else {
|
|
q1 = q2 + (m_original.p1 () - m_original.p2 ());
|
|
}
|
|
|
|
m_current.p1 (q1);
|
|
m_current.p2 (q2);
|
|
|
|
m_rulers [0]->redraw ();
|
|
|
|
} else if (m_move_mode == MoveSelected) {
|
|
|
|
db::DVector dp = p - m_trans (m_p1);
|
|
// round the drag distance to grid if required: this is the least we can do in this case
|
|
if (m_grid_snap) {
|
|
dp = db::DVector (lay::snap (dp.x (), m_grid), lay::snap (dp.y (), m_grid));
|
|
}
|
|
|
|
m_trans = db::DTrans (dp) * m_trans;
|
|
|
|
for (std::vector<ant::View *>::iterator r = m_rulers.begin (); r != m_rulers.end (); ++r) {
|
|
(*r)->transform_by (db::DCplxTrans (m_trans));
|
|
}
|
|
|
|
}
|
|
|
|
if (m_move_mode != MoveSelected) {
|
|
show_message ();
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
Service::show_message ()
|
|
{
|
|
// display current rulers' parameters
|
|
std::string pos = std::string ("lx: ") + tl::micron_to_string (m_current.p2 ().x () - m_current.p1 ().x ())
|
|
+ " ly: " + tl::micron_to_string (m_current.p2 ().y () - m_current.p1 ().y ())
|
|
+ " l: " + tl::micron_to_string (m_current.p2 ().distance (m_current.p1 ()));
|
|
view ()->message (pos);
|
|
}
|
|
|
|
void
|
|
Service::end_move (const db::DPoint &, lay::angle_constraint_type)
|
|
{
|
|
if (! m_rulers.empty () && ! m_selected.empty ()) {
|
|
|
|
if (m_move_mode == MoveSelected) {
|
|
|
|
// replace the rulers that were moved:
|
|
for (std::map<obj_iterator, unsigned int>::const_iterator s = m_selected.begin (); s != m_selected.end (); ++s) {
|
|
|
|
const ant::Object *robj = dynamic_cast<const ant::Object *> (s->first->ptr ());
|
|
if (robj) {
|
|
|
|
// compute moved object and replace
|
|
ant::Object *rnew = new ant::Object (*robj);
|
|
rnew->transform (m_trans);
|
|
mp_view->annotation_shapes ().replace (s->first, db::DUserObject (rnew));
|
|
annotation_changed_event (rnew->id ());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// and make selection "visible"
|
|
selection_to_view ();
|
|
|
|
} else if (m_move_mode != MoveNone) {
|
|
|
|
// replace the ruler that was moved
|
|
mp_view->annotation_shapes ().replace (m_selected.begin ()->first, db::DUserObject (new ant::Object (m_current)));
|
|
annotation_changed_event (m_current.id ());
|
|
|
|
// clear the selection (that was artifically created before)
|
|
clear_selection ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// termine the operation
|
|
m_move_mode = MoveNone;
|
|
|
|
}
|
|
|
|
void
|
|
Service::selection_to_view ()
|
|
{
|
|
annotation_selection_changed_event ();
|
|
|
|
// the selection objects need to be recreated since we destroyed the old rulers
|
|
for (std::vector<ant::View *>::iterator r = m_rulers.begin (); r != m_rulers.end (); ++r) {
|
|
delete *r;
|
|
}
|
|
m_rulers.clear ();
|
|
m_rulers.reserve (m_selected.size ());
|
|
for (std::map<obj_iterator, unsigned int>::iterator r = m_selected.begin (); r != m_selected.end (); ++r) {
|
|
r->second = (unsigned int) m_rulers.size ();
|
|
const ant::Object *robj = dynamic_cast<const ant::Object *> (r->first->ptr ());
|
|
m_rulers.push_back (new ant::View (this, robj, true /*selected*/));
|
|
}
|
|
}
|
|
|
|
db::DBox
|
|
Service::selection_bbox ()
|
|
{
|
|
db::DBox box;
|
|
for (std::map<obj_iterator, unsigned int>::iterator r = m_selected.begin (); r != m_selected.end (); ++r) {
|
|
const ant::Object *robj = dynamic_cast<const ant::Object *> (r->first->ptr ());
|
|
if (robj) {
|
|
box += robj->box ();
|
|
}
|
|
}
|
|
return box;
|
|
}
|
|
|
|
void
|
|
Service::transform (const db::DCplxTrans &trans)
|
|
{
|
|
// replace the rulers that were transformed:
|
|
for (std::map<obj_iterator, unsigned int>::const_iterator s = m_selected.begin (); s != m_selected.end (); ++s) {
|
|
|
|
const ant::Object *robj = dynamic_cast<const ant::Object *> (s->first->ptr ());
|
|
if (robj) {
|
|
|
|
// compute transformed object and replace
|
|
ant::Object *rnew = new ant::Object (*robj);
|
|
rnew->transform (trans);
|
|
mp_view->annotation_shapes ().replace (s->first, db::DUserObject (rnew));
|
|
annotation_changed_event (rnew->id ());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
selection_to_view ();
|
|
}
|
|
|
|
void
|
|
Service::edit_cancel ()
|
|
{
|
|
// Cancel any move operation
|
|
if (m_move_mode != MoveNone) {
|
|
|
|
m_move_mode = MoveNone;
|
|
selection_to_view ();
|
|
|
|
}
|
|
}
|
|
|
|
bool
|
|
Service::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio)
|
|
{
|
|
return mouse_click_event (p, buttons, prio);
|
|
}
|
|
|
|
bool
|
|
Service::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio)
|
|
{
|
|
if (prio && (buttons & lay::LeftButton) != 0) {
|
|
|
|
if (! m_drawing) {
|
|
|
|
// cancel any edit operations so far
|
|
m_move_mode = MoveNone;
|
|
|
|
// reset selection
|
|
clear_selection ();
|
|
|
|
// set the maximum number of rulers minus 1 to account for the new ruler
|
|
// and clear surplus rulers
|
|
reduce_rulers (m_max_number_of_rulers - 1);
|
|
|
|
const ant::Template &tpl = current_template ();
|
|
|
|
// create and start dragging the ruler
|
|
|
|
if (tpl.mode () == ant::Template::RulerSingleClick) {
|
|
|
|
db::DPoint pt = snap1 (p, m_obj_snap && tpl.snap ()).second;
|
|
|
|
// begin the transaction
|
|
tl_assert (! manager ()->transacting ());
|
|
manager ()->transaction (tl::to_string (QObject::tr ("Create ruler")));
|
|
|
|
m_current = ant::Object (pt, pt, 0, tpl);
|
|
show_message ();
|
|
|
|
insert_ruler (m_current, true);
|
|
|
|
// end the transaction
|
|
manager ()->commit ();
|
|
|
|
} else if (tpl.mode () == ant::Template::RulerAutoMetric) {
|
|
|
|
// for auto-metric we need some cutline constraint - any or global won't do.
|
|
lay::angle_constraint_type ac = tpl.angle_constraint ();
|
|
ac = (ac == lay::AC_Global ? m_snap_mode : ac);
|
|
if (ac == lay::AC_Any || ac == lay::AC_Global) {
|
|
ac = lay::AC_Diagonal;
|
|
}
|
|
|
|
db::DVector g;
|
|
if (m_grid_snap) {
|
|
g = db::DVector (m_grid, m_grid);
|
|
}
|
|
|
|
double snap_range = widget ()->mouse_event_trans ().inverted ().ctrans (m_snap_range);
|
|
snap_range *= 0.5;
|
|
|
|
std::pair<bool, db::DEdge> ee = lay::obj_snap2 (mp_view, p, p, g, ac, snap_range, snap_range * 1000.0);
|
|
if (ee.first) {
|
|
|
|
// begin the transaction
|
|
tl_assert (! manager ()->transacting ());
|
|
manager ()->transaction (tl::to_string (QObject::tr ("Create ruler")));
|
|
|
|
m_current = ant::Object (ee.second.p1 (), ee.second.p2 (), 0, tpl);
|
|
show_message ();
|
|
|
|
insert_ruler (m_current, true);
|
|
|
|
// end the transaction
|
|
manager ()->commit ();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
m_p1 = snap1 (p, m_obj_snap && tpl.snap ()).second;
|
|
|
|
m_current = ant::Object (m_p1, m_p1, 0, tpl);
|
|
show_message ();
|
|
|
|
if (mp_active_ruler) {
|
|
delete mp_active_ruler;
|
|
}
|
|
mp_active_ruler = new ant::View (this, &m_current, false /*not selected*/);
|
|
mp_active_ruler->thaw ();
|
|
m_drawing = true;
|
|
|
|
widget ()->grab_mouse (this, false);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// create the ruler object
|
|
|
|
// begin the transaction
|
|
tl_assert (! manager ()->transacting ());
|
|
manager ()->transaction (tl::to_string (QObject::tr ("Create ruler")));
|
|
|
|
show_message ();
|
|
|
|
insert_ruler (ant::Object (m_current.p1 (), m_current.p2 (), 0, current_template ()), true);
|
|
|
|
// stop dragging
|
|
drag_cancel ();
|
|
|
|
// end the transaction
|
|
manager ()->commit ();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
Service::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio)
|
|
{
|
|
if (m_drawing && prio) {
|
|
|
|
set_cursor (lay::Cursor::cross);
|
|
|
|
m_current.p2 (snap2 (m_p1, p, mp_active_ruler->ruler (), ac_from_buttons (buttons)).second);
|
|
mp_active_ruler->redraw ();
|
|
show_message ();
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
Service::deactivated ()
|
|
{
|
|
drag_cancel ();
|
|
}
|
|
|
|
std::pair<bool, db::DPoint>
|
|
Service::snap1 (const db::DPoint &p, bool obj_snap, const std::vector <db::DEdge> &cutlines)
|
|
{
|
|
db::DVector g;
|
|
if (m_grid_snap) {
|
|
g = db::DVector (m_grid, m_grid);
|
|
}
|
|
|
|
double snap_range = widget ()->mouse_event_trans ().inverted ().ctrans (m_snap_range);
|
|
return lay::obj_snap (obj_snap ? mp_view : 0, p, g, snap_range, cutlines);
|
|
}
|
|
|
|
|
|
std::pair <bool, db::DPoint>
|
|
Service::snap2 (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac)
|
|
{
|
|
db::DVector g;
|
|
if (m_grid_snap) {
|
|
g = db::DVector (m_grid, m_grid);
|
|
}
|
|
|
|
double snap_range = widget ()->mouse_event_trans ().inverted ().ctrans (m_snap_range);
|
|
lay::angle_constraint_type snap_mode = ac == lay::AC_Global ? (obj->angle_constraint () == lay::AC_Global ? m_snap_mode : obj->angle_constraint ()) : ac;
|
|
|
|
return lay::obj_snap (m_obj_snap && obj->snap () ? mp_view : 0, p1, p2, g, snap_mode, snap_range);
|
|
}
|
|
|
|
|
|
struct RulerIdComp
|
|
{
|
|
bool operator() (const lay::AnnotationShapes::iterator &a, const lay::AnnotationShapes::iterator &b) const
|
|
{
|
|
return dynamic_cast<const ant::Object &>(*a->ptr ()).id () < dynamic_cast<const ant::Object &>(*b->ptr ()).id ();
|
|
}
|
|
};
|
|
|
|
void
|
|
Service::reduce_rulers (int num)
|
|
{
|
|
lay::AnnotationShapes::iterator rfrom = mp_view->annotation_shapes ().begin ();
|
|
lay::AnnotationShapes::iterator rto = mp_view->annotation_shapes ().end ();
|
|
|
|
size_t n = std::distance (rfrom, rto);
|
|
if (num >= 0 && int (n) > num) {
|
|
|
|
// clear selection
|
|
clear_selection ();
|
|
|
|
// extract all rulers and other objects
|
|
|
|
std::vector <lay::AnnotationShapes::iterator> positions;
|
|
positions.reserve (n);
|
|
|
|
for (lay::AnnotationShapes::iterator r = rfrom; r != rto; ++r) {
|
|
const ant::Object *robj = dynamic_cast <const ant::Object *> (r->ptr ());
|
|
if (robj) {
|
|
positions.push_back (r);
|
|
}
|
|
}
|
|
|
|
// sort so we find the ones that are too old, remove them and sort the
|
|
// remaining positions
|
|
tl::sort (positions.begin (), positions.end (), RulerIdComp ()); // HINT: must be tl::sort, not std::sort because gcc 3.2.3 has some strange namespace resolution problems ..
|
|
positions.erase (positions.begin () + (positions.size () - num), positions.end ());
|
|
tl::sort (positions.begin (), positions.end ()); // HINT: must be tl::sort, not std::sort because gcc 3.2.3 has some strange namespace resolution problems ..
|
|
|
|
// now we can erase these positions
|
|
mp_view->annotation_shapes ().erase_positions (positions.begin (), positions.end ());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
Service::cut ()
|
|
{
|
|
if (selection_size () > 0) {
|
|
|
|
// copy & delete the selected rulers
|
|
copy_selected ();
|
|
del_selected ();
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
Service::copy ()
|
|
{
|
|
// copy the selected rulers
|
|
copy_selected ();
|
|
}
|
|
|
|
void
|
|
Service::copy_selected ()
|
|
{
|
|
// extract all selected rulers and paste in "micron" space
|
|
for (std::map<obj_iterator, unsigned int>::iterator r = m_selected.begin (); r != m_selected.end (); ++r) {
|
|
r->second = (unsigned int) m_rulers.size ();
|
|
const ant::Object *robj = dynamic_cast<const ant::Object *> (r->first->ptr ());
|
|
if (robj) {
|
|
db::Clipboard::instance () += new db::ClipboardValue<ant::Object> (*robj);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Service::paste ()
|
|
{
|
|
if (db::Clipboard::instance ().begin () != db::Clipboard::instance ().end ()) {
|
|
|
|
// determine the last id
|
|
int idmax = -1;
|
|
for (lay::AnnotationShapes::iterator r = mp_view->annotation_shapes ().begin (); r != mp_view->annotation_shapes ().end (); ++r) {
|
|
const ant::Object *robj = dynamic_cast <const ant::Object *> (r->ptr ());
|
|
if (robj && robj->id () > idmax) {
|
|
idmax = robj->id ();
|
|
}
|
|
}
|
|
|
|
for (db::Clipboard::iterator c = db::Clipboard::instance ().begin (); c != db::Clipboard::instance ().end (); ++c) {
|
|
const db::ClipboardValue<ant::Object> *value = dynamic_cast<const db::ClipboardValue<ant::Object> *> (*c);
|
|
if (value) {
|
|
ant::Object *ruler = new ant::Object (value->get ());
|
|
ruler->id (++idmax);
|
|
mp_view->annotation_shapes ().insert (db::DUserObject (ruler));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
Service::del ()
|
|
{
|
|
if (selection_size () > 0) {
|
|
|
|
// delete the selected rulers
|
|
del_selected ();
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
Service::del_selected ()
|
|
{
|
|
// positions will hold a set of iterators that are to be erased
|
|
std::vector <lay::AnnotationShapes::iterator> positions;
|
|
positions.reserve (m_selected.size ());
|
|
for (std::map<obj_iterator, unsigned int>::iterator r = m_selected.begin (); r != m_selected.end (); ++r) {
|
|
positions.push_back (r->first);
|
|
}
|
|
|
|
// clear selection
|
|
clear_selection ();
|
|
|
|
// erase all and insert the ones that we want to keep
|
|
tl::sort (positions.begin (), positions.end ()); // HINT: must be tl::sort, not std::sort because gcc 3.2.3 has some strange namespace resolution problems ..
|
|
mp_view->annotation_shapes ().erase_positions (positions.begin (), positions.end ());
|
|
}
|
|
|
|
size_t
|
|
Service::selection_size ()
|
|
{
|
|
return m_selected.size ();
|
|
}
|
|
|
|
bool
|
|
Service::select (obj_iterator obj, lay::Editable::SelectionMode mode)
|
|
{
|
|
if (mode == lay::Editable::Replace || mode == lay::Editable::Add) {
|
|
// select
|
|
if (m_selected.find (obj) == m_selected.end ()) {
|
|
m_selected.insert (std::make_pair (obj, 0));
|
|
return true;
|
|
}
|
|
} else if (mode == lay::Editable::Reset) {
|
|
// unselect
|
|
if (m_selected.find (obj) != m_selected.end ()) {
|
|
m_selected.erase (obj);
|
|
return true;
|
|
}
|
|
} else {
|
|
// invert selection
|
|
if (m_selected.find (obj) != m_selected.end ()) {
|
|
m_selected.erase (obj);
|
|
} else {
|
|
m_selected.insert (std::make_pair (obj, 0));
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
Service::clear_selection ()
|
|
{
|
|
select (db::DBox (), lay::Editable::Reset);
|
|
}
|
|
|
|
static bool
|
|
is_selected (const ant::Object &ruler, const db::DPoint &pos, double enl, double &distance)
|
|
{
|
|
if (ruler.outline () == ant::Object::OL_ellipse) {
|
|
|
|
// special handling of the (non-degenerated) ellipse case
|
|
db::DBox b (ruler.p1 (), ruler.p2 ());
|
|
|
|
if (b.height () > 1e-6 && b.width () > 1e-6) {
|
|
|
|
double dx = (pos.x () - b.center ().x ()) / (b.width () * 0.5);
|
|
double dy = (pos.y () - b.center ().y ()) / (b.height () * 0.5);
|
|
double dd = sqrt (dx * dx + dy * dy);
|
|
|
|
if (dd > 1e-6) {
|
|
// ref is the cutpoint between the ray between pos and the ellipse center and the ellipse itself
|
|
db::DPoint ref = b.center () + db::DVector (dx * b.width () * 0.5 / dd, dy * b.height () * 0.5 / dd);
|
|
double d = ref.distance (pos);
|
|
if (d < enl) {
|
|
distance = d;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
db::DBox b (ruler.p1 (), ruler.p2 ());
|
|
|
|
// enlarge this box by some pixels
|
|
b.enlarge (db::DVector (enl, enl));
|
|
|
|
if (! b.contains (pos)) {
|
|
return false;
|
|
}
|
|
|
|
db::DEdge edges[4];
|
|
unsigned int nedges = 0;
|
|
|
|
if (ruler.outline () == ant::Object::OL_diag ||
|
|
ruler.outline () == ant::Object::OL_diag_xy ||
|
|
ruler.outline () == ant::Object::OL_diag_yx) {
|
|
edges [nedges++] = db::DEdge (ruler.p1 (), ruler.p2 ());
|
|
}
|
|
if (ruler.outline () == ant::Object::OL_xy ||
|
|
ruler.outline () == ant::Object::OL_diag_xy ||
|
|
ruler.outline () == ant::Object::OL_box ||
|
|
ruler.outline () == ant::Object::OL_ellipse) {
|
|
edges [nedges++] = db::DEdge (ruler.p1 (), db::DPoint (ruler.p2 ().x (), ruler.p1 ().y ()));
|
|
edges [nedges++] = db::DEdge (db::DPoint (ruler.p2 ().x (), ruler.p1 ().y ()), ruler.p2 ());
|
|
}
|
|
if (ruler.outline () == ant::Object::OL_yx ||
|
|
ruler.outline () == ant::Object::OL_diag_yx ||
|
|
ruler.outline () == ant::Object::OL_box ||
|
|
ruler.outline () == ant::Object::OL_ellipse) {
|
|
edges [nedges++] = db::DEdge (ruler.p1 (), db::DPoint (ruler.p1 ().x (), ruler.p2 ().y ()));
|
|
edges [nedges++] = db::DEdge (db::DPoint (ruler.p1 ().x (), ruler.p2 ().y ()), ruler.p2 ());
|
|
}
|
|
|
|
for (unsigned int i = 0; i < nedges; ++i) {
|
|
double d = edges [i].distance_abs (pos);
|
|
if (d <= enl) {
|
|
distance = d;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
is_selected (const ant::Object &ruler, const db::DBox &box, double /*enl*/)
|
|
{
|
|
return (box.contains (ruler.p1 ()) && box.contains (ruler.p2 ()));
|
|
}
|
|
|
|
double
|
|
Service::click_proximity (const db::DPoint &pos, lay::Editable::SelectionMode mode)
|
|
{
|
|
// compute search box
|
|
double l = double (search_range) / widget ()->mouse_event_trans ().mag ();
|
|
db::DBox search_dbox = db::DBox (pos, pos).enlarged (db::DVector (l, l));
|
|
|
|
// for single-point selections either exclude the current selection or the
|
|
// accumulated previous selection from the search.
|
|
const std::map<obj_iterator, unsigned int> *exclude = 0;
|
|
if (mode == lay::Editable::Replace) {
|
|
exclude = &m_previous_selection;
|
|
} else if (mode == lay::Editable::Add) {
|
|
exclude = &m_selected;
|
|
} else if (mode == lay::Editable::Reset) {
|
|
// TODO: the finder should favor the current selection in this case.
|
|
}
|
|
|
|
// point selection: look for the "closest" ruler
|
|
double dmin = std::numeric_limits <double>::max ();
|
|
bool any_found = false;
|
|
|
|
lay::AnnotationShapes::touching_iterator r = mp_view->annotation_shapes ().begin_touching (search_dbox);
|
|
while (! r.at_end ()) {
|
|
const ant::Object *robj = dynamic_cast<const ant::Object *> ((*r).ptr ());
|
|
if (robj && (! exclude || exclude->find (mp_view->annotation_shapes ().iterator_from_pointer (&*r)) == exclude->end ())) {
|
|
double d;
|
|
if (is_selected (*robj, pos, l, d)) {
|
|
if (! any_found || d < dmin) {
|
|
dmin = d;
|
|
}
|
|
any_found = true;
|
|
}
|
|
}
|
|
++r;
|
|
}
|
|
|
|
// return the proximity value
|
|
if (any_found) {
|
|
return dmin;
|
|
} else {
|
|
return lay::Editable::click_proximity (pos, mode);
|
|
}
|
|
}
|
|
|
|
bool
|
|
Service::transient_select (const db::DPoint &pos)
|
|
{
|
|
clear_transient_selection ();
|
|
|
|
// if in move mode (which also receives transient_select requests) the move will take the selection,
|
|
// hence don't do a transient selection if there is one.
|
|
if (view ()->has_selection () && view ()->is_move_mode ()) {
|
|
return false;
|
|
}
|
|
|
|
bool any_selected = false;
|
|
|
|
// compute search box
|
|
double l = double (search_range) / widget ()->mouse_event_trans ().mag ();
|
|
db::DBox search_dbox = db::DBox (pos, pos).enlarged (db::DVector (l, l));
|
|
|
|
// point selection: look for the "closest" ruler
|
|
double dmin = std::numeric_limits <double>::max ();
|
|
|
|
lay::AnnotationShapes::touching_iterator r = mp_view->annotation_shapes ().begin_touching (search_dbox);
|
|
lay::AnnotationShapes::touching_iterator rmin (r);
|
|
while (! r.at_end ()) {
|
|
const ant::Object *robj = dynamic_cast<const ant::Object *> ((*r).ptr ());
|
|
if (robj && m_previous_selection.find (mp_view->annotation_shapes ().iterator_from_pointer (&*r)) == m_previous_selection.end ()) {
|
|
double d;
|
|
if (is_selected (*robj, pos, l, d)) {
|
|
if (! any_selected || d < dmin) {
|
|
rmin = r;
|
|
dmin = d;
|
|
}
|
|
any_selected = true;
|
|
}
|
|
}
|
|
++r;
|
|
}
|
|
|
|
// create the transient marker for the object found
|
|
if (any_selected) {
|
|
const ant::Object *robj = dynamic_cast<const ant::Object *> ((*rmin).ptr ());
|
|
// HINT: there is no special style for "transient selection on rulers"
|
|
mp_transient_ruler = new ant::View (this, robj, true /*selected*/);
|
|
}
|
|
|
|
if (any_selected && editables ()->selection_size () == 0) {
|
|
display_status (true);
|
|
}
|
|
|
|
return any_selected;
|
|
}
|
|
|
|
void
|
|
Service::clear_transient_selection ()
|
|
{
|
|
if (mp_transient_ruler) {
|
|
delete mp_transient_ruler;
|
|
mp_transient_ruler = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
Service::clear_previous_selection ()
|
|
{
|
|
m_previous_selection.clear ();
|
|
}
|
|
|
|
bool
|
|
Service::select (const db::DBox &box, lay::Editable::SelectionMode mode)
|
|
{
|
|
bool needs_update = false;
|
|
bool any_selected = false;
|
|
|
|
// clear before unless "add" is selected
|
|
if (mode == lay::Editable::Replace) {
|
|
if (! m_selected.empty ()) {
|
|
m_selected.clear ();
|
|
needs_update = true;
|
|
}
|
|
}
|
|
|
|
// for single-point selections either exclude the current selection or the
|
|
// accumulated previous selection from the search.
|
|
const std::map<obj_iterator, unsigned int> *exclude = 0;
|
|
if (mode == lay::Editable::Replace) {
|
|
exclude = &m_previous_selection;
|
|
} else if (mode == lay::Editable::Add) {
|
|
exclude = &m_selected;
|
|
} else if (mode == lay::Editable::Reset) {
|
|
// TODO: the finder should favor the current selection in this case.
|
|
}
|
|
|
|
if (box.empty ()) {
|
|
|
|
// unconditional selection
|
|
if (mode == lay::Editable::Reset) {
|
|
if (! m_selected.empty ()) {
|
|
m_selected.clear ();
|
|
needs_update = true;
|
|
}
|
|
} else {
|
|
|
|
lay::AnnotationShapes::iterator rfrom = mp_view->annotation_shapes ().begin ();
|
|
lay::AnnotationShapes::iterator rto = mp_view->annotation_shapes ().end ();
|
|
|
|
// extract all rulers
|
|
for (lay::AnnotationShapes::iterator r = rfrom; r != rto; ++r) {
|
|
const ant::Object *robj = dynamic_cast<const ant::Object *> ((*r).ptr ());
|
|
if (robj) {
|
|
any_selected = true;
|
|
if (select (r, mode)) {
|
|
needs_update = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
// compute search box
|
|
double l = double (search_range) / widget ()->mouse_event_trans ().mag ();
|
|
db::DBox search_dbox = box.enlarged (db::DVector (l, l));
|
|
|
|
if (! box.is_point ()) {
|
|
|
|
// box-selection
|
|
lay::AnnotationShapes::touching_iterator r = mp_view->annotation_shapes ().begin_touching (search_dbox);
|
|
while (! r.at_end ()) {
|
|
const ant::Object *robj = dynamic_cast<const ant::Object *> ((*r).ptr ());
|
|
if (robj && (! exclude || exclude->find (mp_view->annotation_shapes ().iterator_from_pointer (&*r)) == exclude->end ())) {
|
|
if (is_selected (*robj, box, l)) {
|
|
any_selected = true;
|
|
if (select (mp_view->annotation_shapes ().iterator_from_pointer (&*r), mode)) {
|
|
needs_update = true;
|
|
}
|
|
}
|
|
}
|
|
++r;
|
|
}
|
|
|
|
} else {
|
|
|
|
// point selection: look for the "closest" ruler
|
|
double dmin = std::numeric_limits <double>::max ();
|
|
|
|
lay::AnnotationShapes::touching_iterator r = mp_view->annotation_shapes ().begin_touching (search_dbox);
|
|
lay::AnnotationShapes::touching_iterator rmin (r);
|
|
while (! r.at_end ()) {
|
|
const ant::Object *robj = dynamic_cast<const ant::Object *> ((*r).ptr ());
|
|
if (robj && (! exclude || exclude->find (mp_view->annotation_shapes ().iterator_from_pointer (&*r)) == exclude->end ())) {
|
|
double d;
|
|
if (is_selected (*robj, box.p1 (), l, d)) {
|
|
if (! any_selected || d < dmin) {
|
|
rmin = r;
|
|
dmin = d;
|
|
}
|
|
any_selected = true;
|
|
}
|
|
}
|
|
++r;
|
|
}
|
|
|
|
// select the one that was found
|
|
if (any_selected) {
|
|
select (mp_view->annotation_shapes ().iterator_from_pointer (&*rmin), mode);
|
|
m_previous_selection.insert (std::make_pair (mp_view->annotation_shapes ().iterator_from_pointer (&*rmin), mode));
|
|
needs_update = true;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if required, update the list of ruler objects to display the selection
|
|
if (needs_update) {
|
|
selection_to_view ();
|
|
}
|
|
|
|
if (any_selected) {
|
|
display_status (false);
|
|
}
|
|
|
|
// return true if at least one element was selected
|
|
return any_selected;
|
|
}
|
|
|
|
void
|
|
Service::display_status (bool transient)
|
|
{
|
|
View *selected_view = transient ? mp_transient_ruler : (m_rulers.size () == 1 ? m_rulers [0] : 0);
|
|
if (! selected_view) {
|
|
view ()->message (std::string ());
|
|
} else {
|
|
|
|
const ant::Object *ruler = selected_view->ruler ();
|
|
|
|
std::string msg;
|
|
if (! transient) {
|
|
msg = tl::to_string (QObject::tr ("selected: "));
|
|
}
|
|
msg += tl::sprintf (tl::to_string (QObject::tr ("annotation(d=%s x=%s y=%s)")), ruler->text (), ruler->text_x (), ruler->text_y ());
|
|
view ()->message (msg);
|
|
|
|
}
|
|
}
|
|
|
|
lay::PropertiesPage *
|
|
Service::properties_page (QWidget *parent)
|
|
{
|
|
return new PropertiesPage (this, parent);
|
|
}
|
|
|
|
void
|
|
Service::get_selection (std::vector <obj_iterator> &sel) const
|
|
{
|
|
sel.clear ();
|
|
sel.reserve (m_selected.size ());
|
|
|
|
// positions will hold a set of iterators that are to be erased
|
|
for (std::map<obj_iterator, unsigned int>::const_iterator r = m_selected.begin (); r != m_selected.end (); ++r) {
|
|
sel.push_back (r->first);
|
|
}
|
|
}
|
|
|
|
void
|
|
Service::delete_ruler (obj_iterator pos)
|
|
{
|
|
// delete the object
|
|
m_selected.erase (pos);
|
|
mp_view->annotation_shapes ().erase (pos);
|
|
|
|
// and make selection "visible"
|
|
selection_to_view ();
|
|
}
|
|
|
|
void
|
|
Service::change_ruler (obj_iterator pos, const ant::Object &to)
|
|
{
|
|
// replace the object, keep the ID:
|
|
ant::Object *new_ruler = new ant::Object (to);
|
|
const ant::Object *current_ruler = dynamic_cast <const ant::Object *> (pos->ptr ());
|
|
tl_assert (current_ruler != 0);
|
|
new_ruler->id (current_ruler->id ());
|
|
mp_view->annotation_shapes ().replace (pos, db::DUserObject (new_ruler));
|
|
|
|
annotation_changed_event (new_ruler->id ());
|
|
|
|
// and make selection "visible"
|
|
selection_to_view ();
|
|
}
|
|
|
|
void
|
|
Service::paint_on_planes (const db::DCplxTrans &trans,
|
|
const std::vector <lay::CanvasPlane *> &planes,
|
|
lay::Renderer &renderer)
|
|
{
|
|
if (planes.empty ()) {
|
|
return;
|
|
}
|
|
|
|
db::DBox vp = db::DBox (trans.inverted () * db::DBox (db::DPoint (0.0, 0.0), db::DPoint (renderer.width (), renderer.height ())));
|
|
|
|
lay::AnnotationShapes::touching_iterator user_object = mp_view->annotation_shapes ().begin_touching (vp);
|
|
while (! user_object.at_end ()) {
|
|
const ant::Object *ruler = dynamic_cast <const ant::Object *> ((*user_object).ptr ());
|
|
if (ruler) {
|
|
draw_ruler (*ruler, trans, false /*not selected*/, planes.front (), renderer);
|
|
}
|
|
++user_object;
|
|
}
|
|
}
|
|
|
|
AnnotationIterator
|
|
Service::begin_annotations () const
|
|
{
|
|
return AnnotationIterator (mp_view->annotation_shapes ().begin (), mp_view->annotation_shapes ().end ());
|
|
}
|
|
|
|
void
|
|
Service::menu_activated (const std::string &symbol)
|
|
{
|
|
if (symbol == "ant::clear_all_rulers_internal") {
|
|
clear_rulers ();
|
|
} else if (symbol == "ant::clear_all_rulers") {
|
|
manager ()->transaction (tl::to_string (QObject::tr ("Clear all rulers")));
|
|
clear_rulers ();
|
|
manager ()->commit ();
|
|
} else {
|
|
lay::Plugin::menu_activated (symbol);
|
|
}
|
|
}
|
|
|
|
void
|
|
Service::register_annotation_template (const ant::Template &t)
|
|
{
|
|
std::string value = lay::PluginRoot::instance ()->config_get (cfg_ruler_templates);
|
|
|
|
std::vector<ant::Template> templates = ant::Template::from_string (value);
|
|
|
|
// Remove a template with the same category if such a template already exists
|
|
if (! t.category ().empty ()) {
|
|
for (size_t i = 0; i < templates.size (); ) {
|
|
if (templates[i].category () == t.category ()) {
|
|
templates.erase (templates.begin () + i);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// and add the new one
|
|
templates.push_back (t);
|
|
|
|
value = ant::Template::to_string (templates);
|
|
|
|
lay::PluginRoot::instance ()->config_set (cfg_ruler_templates, value);
|
|
lay::PluginRoot::instance ()->config_end ();
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
|
|
} // namespace ant
|
|
|
|
|