diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index 37ead1bac..6f6c0e556 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -1431,7 +1431,7 @@ Service::begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::ang const ant::Object *robj = dynamic_cast ((*ri).ptr ()); if (robj && (! robj_min || robj == robj_min)) { - if (dragging_what (robj, search_dbox, m_move_mode, m_p1, m_seg_index) && m_move_mode != MoveRuler) { + if (dragging_what (robj, search_dbox, m_move_mode, m_p1, m_seg_index)) { // found anything: make the moved ruler the selection clear_selection (); @@ -1516,28 +1516,70 @@ Service::begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::ang } void -Service::move_transform (const db::DPoint &p, db::DFTrans tr, lay::angle_constraint_type /*ac*/) +Service::snap_rulers (lay::angle_constraint_type ac) { - if (m_rulers.empty () || m_selected.empty ()) { + if (m_rulers.empty ()) { return; } - if (m_move_mode == MoveRuler) { + lay::PointSnapToObjectResult min_snp; + double min_dist = -1.0; + db::DVector min_delta; - db::DVector dp = p - db::DPoint (); + for (auto r = m_rulers.begin (); r != m_rulers.end (); ++r) { - 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)); + const ant::Object *ruler = (*r)->ruler (); - // display current rulers' parameters - show_message (); + db::DPoint p1 = m_trans * ruler->p1 (); + db::DPoint p2 = m_trans * ruler->p2 (); - m_rulers [0]->redraw (); + auto tr = db::DTrans ((m_p1 - db::DPoint ()) - m_trans.disp ()) * m_trans * db::DTrans (db::DPoint () - m_p1); + db::DPoint org1 = tr * ruler->p1 (); + db::DPoint org2 = tr * ruler->p2 (); - } else if (m_move_mode == MoveSelected) { + auto snp = snap2_details (org1, p1, ruler, ac); + double dist = p1.distance (snp.snapped_point); + + if (min_dist < 0 || dist < min_dist) { + min_snp = snp; + min_dist = dist; + min_delta = snp.snapped_point - p1; + } + + snp = snap2_details (org2, p2, ruler, ac); + dist = p2.distance (snp.snapped_point); + + if (min_dist < 0 || dist < min_dist) { + min_snp = snp; + min_dist = dist; + min_delta = snp.snapped_point - p2; + } + + } + + if (min_snp.object_snap != lay::PointSnapToObjectResult::NoObject) { + mouse_cursor_from_snap_details (min_snp); + } + + m_trans = db::DTrans (min_delta) * m_trans; +} + +void +Service::move_transform (const db::DPoint & /*p*/, db::DFTrans tr, lay::angle_constraint_type ac) +{ + if (m_rulers.empty () || m_selected.empty ()) { + return; + } + + auto ac_eff = ac == lay::AC_Global ? m_snap_mode : ac; + clear_mouse_cursors (); + + if (m_move_mode == MoveSelected) { m_trans *= db::DTrans (m_p1 - db::DPoint ()) * db::DTrans (tr) * db::DTrans (db::DPoint () - m_p1); + snap_rulers (ac_eff); + for (std::vector::iterator r = m_rulers.begin (); r != m_rulers.end (); ++r) { (*r)->transform_by (db::DCplxTrans (m_trans)); } @@ -1553,90 +1595,66 @@ Service::move (const db::DPoint &p, lay::angle_constraint_type ac) return; } + auto ac_eff = ac == lay::AC_Global ? m_snap_mode : ac; + clear_mouse_cursors (); + if (m_move_mode == MoveP1) { - m_current.seg_p1 (m_seg_index, snap2 (m_p1, p, &m_current, ac).second); + m_current.seg_p1 (m_seg_index, snap2_visual (m_p1, p, &m_current, ac)); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP2) { - m_current.seg_p2 (m_seg_index, snap2 (m_p1, p, &m_current, ac).second); + m_current.seg_p2 (m_seg_index, snap2_visual (m_p1, p, &m_current, ac)); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP12) { - db::DPoint p12 = snap2 (m_p1, p, &m_current, ac).second; + db::DPoint p12 = snap2_visual (m_p1, p, &m_current, ac); m_current.seg_p1 (m_seg_index, db::DPoint (m_current.seg_p1 (m_seg_index).x(), p12.y ())); m_current.seg_p2 (m_seg_index, db::DPoint (p12.x (), m_current.seg_p2 (m_seg_index).y ())); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP21) { - db::DPoint p21 = snap2 (m_p1, p, &m_current, ac).second; + db::DPoint p21 = snap2_visual (m_p1, p, &m_current, ac); m_current.seg_p1 (m_seg_index, db::DPoint (p21.x (), m_current.seg_p1 (m_seg_index).y ())); m_current.seg_p2 (m_seg_index, db::DPoint (m_current.seg_p2 (m_seg_index).x(), p21.y ())); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP1X) { - db::DPoint pc = snap2 (m_p1, p, &m_current, ac).second; + db::DPoint pc = snap2_visual (m_p1, p, &m_current, ac); m_current.seg_p1 (m_seg_index, db::DPoint (pc.x (), m_current.seg_p1 (m_seg_index).y ())); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP2X) { - db::DPoint pc = snap2 (m_p1, p, &m_current, ac).second; + db::DPoint pc = snap2_visual (m_p1, p, &m_current, ac); m_current.seg_p2 (m_seg_index, db::DPoint (pc.x (), m_current.seg_p2 (m_seg_index).y ())); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP1Y) { - db::DPoint pc = snap2 (m_p1, p, &m_current, ac).second; + db::DPoint pc = snap2_visual (m_p1, p, &m_current, ac); m_current.seg_p1 (m_seg_index, db::DPoint (m_current.seg_p1 (m_seg_index).x (), pc.y ())); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP2Y) { - db::DPoint pc = snap2 (m_p1, p, &m_current, ac).second; + db::DPoint pc = snap2_visual (m_p1, p, &m_current, ac); m_current.seg_p2 (m_seg_index, db::DPoint (m_current.seg_p2 (m_seg_index).x (), pc.y ())); m_rulers [0]->redraw (); - } else if (m_move_mode == MoveRuler) { - - // try two ways of snapping - db::DVector dp = lay::snap_angle (p - m_p1, ac == lay::AC_Global ? m_snap_mode : ac); - - db::DPoint p1 = m_original.p1 () + dp; - db::DPoint p2 = m_original.p2 () + dp; - - std::pair r1 = snap1 (p1, m_obj_snap && m_original.snap ()); - db::DPoint q1 = r1.second; - std::pair 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_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)); - } - - dp = lay::snap_angle (dp, ac == lay::AC_Global ? m_snap_mode : ac); + dp = lay::snap_angle (dp, ac_eff); m_trans = db::DTrans (dp + (m_p1 - db::DPoint ()) - m_trans.disp ()) * m_trans * db::DTrans (db::DPoint () - m_p1); + snap_rulers (ac_eff); + for (std::vector::iterator r = m_rulers.begin (); r != m_rulers.end (); ++r) { (*r)->transform_by (db::DCplxTrans (m_trans)); } @@ -1646,7 +1664,6 @@ Service::move (const db::DPoint &p, lay::angle_constraint_type ac) if (m_move_mode != MoveSelected) { show_message (); } - } void @@ -1703,6 +1720,7 @@ Service::end_move (const db::DPoint &, lay::angle_constraint_type) // termine the operation m_move_mode = MoveNone; + clear_mouse_cursors (); } void @@ -1766,6 +1784,7 @@ Service::edit_cancel () if (m_move_mode != MoveNone) { m_move_mode = MoveNone; + m_selected.clear (); selection_to_view (); } @@ -2039,7 +2058,7 @@ Service::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) // otherwise we risk manipulating p1 too. ant::Object::point_list pts = m_current.points (); if (! pts.empty ()) { - pts.back () = snap2 (m_p1, p, mp_active_ruler->ruler (), ac_from_buttons (buttons)).second; + pts.back () = snap_details.snapped_point; } m_current.set_points_exact (pts); @@ -2094,11 +2113,16 @@ Service::snap2_details (const db::DPoint &p1, const db::DPoint &p2, const ant::O return lay::obj_snap (m_obj_snap && obj->snap () ? mp_view : 0, p1, p2, g, snap_mode, snap_range); } -std::pair -Service::snap2 (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac) +db::DPoint +Service::snap2_visual (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac) { lay::PointSnapToObjectResult res = snap2_details (p1, p2, obj, ac); - return std::make_pair (res.object_snap != lay::PointSnapToObjectResult::NoObject, res.snapped_point); + + if (res.object_snap != lay::PointSnapToObjectResult::NoObject) { + mouse_cursor_from_snap_details (res); + } + + return res.snapped_point; } diff --git a/src/ant/ant/antService.h b/src/ant/ant/antService.h index 509d00289..13086eeb4 100644 --- a/src/ant/ant/antService.h +++ b/src/ant/ant/antService.h @@ -208,10 +208,9 @@ Q_OBJECT * MoveP2X - dragging P2.x (if box-like) * MoveP1Y - dragging P1.y (if box-like) * MoveP2Y - dragging P2.y (if box-like) - * MoveRuler - dragging a whole ruler (one) * MoveSelection - dragging a whole ruler (many) */ - enum MoveMode { MoveNone, MoveP1, MoveP2, MoveP12, MoveP21, MoveP1X, MoveP2X, MoveP1Y, MoveP2Y, MoveRuler, MoveSelected }; + enum MoveMode { MoveNone, MoveP1, MoveP2, MoveP12, MoveP21, MoveP1X, MoveP2X, MoveP1Y, MoveP2Y, MoveSelected }; Service (db::Manager *manager, lay::LayoutViewBase *view); @@ -601,7 +600,7 @@ public slots: std::pair snap1 (const db::DPoint &p, bool obj_snap); lay::PointSnapToObjectResult snap1_details (const db::DPoint &p, bool obj_snap); - std::pair snap2 (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac); + db::DPoint snap2_visual (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac); lay::PointSnapToObjectResult snap2_details (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac); lay::TwoPointSnapToObjectResult auto_measure (const db::DPoint &p, lay::angle_constraint_type ac, const ant::Template &tpl); @@ -620,6 +619,8 @@ public slots: virtual bool mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio); virtual void deactivated (); + void snap_rulers (lay::angle_constraint_type ac); + /** * @brief Select a certain ruler * diff --git a/src/db/db/dbFillTool.cc b/src/db/db/dbFillTool.cc index 04358f6e1..bf8abba3e 100644 --- a/src/db/db/dbFillTool.cc +++ b/src/db/db/dbFillTool.cc @@ -42,10 +42,18 @@ class GenericRasterizer // .. nothing yet .. } - GenericRasterizer (const db::Polygon &fp, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, const db::Vector &dim) + GenericRasterizer (const std::vector &fr, const db::Box &rasterized_area, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, const db::Vector &dim) : m_row_step (row_step), m_column_step (column_step), m_row_steps (0), m_column_steps (0), m_origin (origin), m_dim (dim) { - rasterize (fp); + rasterize (rasterized_area, fr); + } + + GenericRasterizer (const db::Polygon &fp, const db::Box &rasterized_area, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, const db::Vector &dim) + : m_row_step (row_step), m_column_step (column_step), m_row_steps (0), m_column_steps (0), m_origin (origin), m_dim (dim) + { + std::vector fr; + fr.push_back (fp); + rasterize (rasterized_area, fr); } void move (const db::Vector &d) @@ -59,7 +67,44 @@ class GenericRasterizer m_area_maps.clear (); } - void rasterize (const db::Polygon &fp) + const db::Point &p0 () const { return m_origin; } + + unsigned int row_steps () const { return m_row_steps; } + unsigned int column_steps () const { return m_column_steps; } + + unsigned int area_maps () const + { + return (unsigned int) m_area_maps.size (); + } + + int index_for_p0 (const db::Point &p0) const + { + for (auto i = m_area_maps.begin (); i != m_area_maps.end (); ++i) { + if (i->p0 () == p0) { + return int (i - m_area_maps.begin ()); + } + } + return -1; + } + + const db::AreaMap &area_map (unsigned int i) const + { + return m_area_maps [i]; + } + + db::AreaMap &area_map (unsigned int i) + { + return m_area_maps [i]; + } + +private: + std::vector m_area_maps; + db::Vector m_row_step, m_column_step; + unsigned int m_row_steps, m_column_steps; + db::Point m_origin; + db::Vector m_dim; + + void rasterize (const db::Box &rasterized_area, const std::vector &fr) { db::Coord dx = m_row_step.x (); db::Coord dy = m_column_step.y (); @@ -81,12 +126,12 @@ class GenericRasterizer m_row_steps *= (m_dim.x () - 1) / (m_row_steps * m_row_step.x ()) + 1; m_column_steps *= (m_dim.y () - 1) / (m_column_steps * m_column_step.y ()) + 1; - db::Box fp_bbox = fp.box (); + db::Box ra_org = rasterized_area; // compensate for distortion by sheared kernel db::Coord ex = std::max (std::abs (db::Coord (m_column_step.x () * m_column_steps)), std::abs (db::Coord (m_row_step.x () * m_row_steps))); db::Coord ey = std::max (std::abs (db::Coord (m_column_step.y () * m_column_steps)), std::abs (db::Coord (m_row_step.y () * m_row_steps))); - fp_bbox.enlarge (db::Vector (ex, ey)); + ra_org.enlarge (db::Vector (ex, ey)); int columns_per_rows = (int (m_row_steps) * m_row_step.y ()) / dy; int rows_per_columns = (int (m_column_steps) * m_column_step.x ()) / dx; @@ -95,16 +140,16 @@ class GenericRasterizer db::Coord ddy = dy * db::Coord (m_column_steps) - m_row_step.y () * rows_per_columns; // round polygon bbox - db::Coord fp_left = db::Coord (tl::round_down (fp_bbox.left () - m_origin.x (), ddx)) + m_origin.x (); - db::Coord fp_bottom = db::Coord (tl::round_down (fp_bbox.bottom () - m_origin.y (), ddy)) + m_origin.y (); - db::Coord fp_right = db::Coord (tl::round_up (fp_bbox.right () - m_origin.x (), ddx)) + m_origin.x (); - db::Coord fp_top = db::Coord (tl::round_up (fp_bbox.top () - m_origin.y (), ddy)) + m_origin.y (); - fp_bbox = db::Box (fp_left, fp_bottom, fp_right, fp_top); + db::Coord ra_left = db::Coord (tl::round_down (ra_org.left () - m_origin.x (), ddx)) + m_origin.x (); + db::Coord ra_bottom = db::Coord (tl::round_down (ra_org.bottom () - m_origin.y (), ddy)) + m_origin.y (); + db::Coord ra_right = db::Coord (tl::round_up (ra_org.right () - m_origin.x (), ddx)) + m_origin.x (); + db::Coord ra_top = db::Coord (tl::round_up (ra_org.top () - m_origin.y (), ddy)) + m_origin.y (); + db::Box ra = db::Box (ra_left, ra_bottom, ra_right, ra_top); - size_t nx = fp_bbox.width () / ddx; - size_t ny = fp_bbox.height () / ddy; + size_t nx = ra.width () / ddx; + size_t ny = ra.height () / ddy; - tl_assert (fp.box ().inside (fp_bbox)); + tl_assert (ra_org.inside (ra)); if (nx == 0 || ny == 0) { // nothing to rasterize: @@ -122,9 +167,15 @@ class GenericRasterizer db::Vector dr = m_row_step * long (ir); db::Vector dc = m_column_step * long (ic); - am.reinitialize (db::Point (fp_left, fp_bottom) + dr + dc, db::Vector (ddx, ddy), m_dim, nx, ny); + am.reinitialize (db::Point (ra_left, ra_bottom) + dr + dc, db::Vector (ddx, ddy), m_dim, nx, ny); - if (db::rasterize (fp, am)) { + bool any = false; + for (auto i = fr.begin (); i != fr.end (); ++i) { + if (db::rasterize (*i, am)) { + any = true; + } + } + if (any) { m_area_maps.push_back (db::AreaMap ()); m_area_maps.back ().swap (am); } @@ -142,9 +193,15 @@ class GenericRasterizer db::Vector dr = m_row_step * long ((rows_per_columns > 0 ? -int (ir + 1) : ir) + m_row_steps); db::Vector dc = m_column_step * long ((columns_per_rows > 0 ? -int (ic + 1) : ic) + m_column_steps); - am.reinitialize (db::Point (fp_left, fp_bottom) + dr + dc, db::Vector (ddx, ddy), m_dim, nx, ny); + am.reinitialize (db::Point (ra_left, ra_bottom) + dr + dc, db::Vector (ddx, ddy), m_dim, nx, ny); - if (db::rasterize (fp, am)) { + bool any = false; + for (auto i = fr.begin (); i != fr.end (); ++i) { + if (db::rasterize (*i, am)) { + any = true; + } + } + if (any) { m_area_maps.push_back (db::AreaMap ()); m_area_maps.back ().swap (am); } @@ -153,39 +210,107 @@ class GenericRasterizer } } +}; - const db::Point &p0 () const { return m_origin; } +static size_t +create_instances (GenericRasterizer &am, db::Cell *cell, db::cell_index_type fill_cell_index, const db::Vector &kernel_origin, const db::Vector &fill_margin, const GenericRasterizer *exclude_rasterized, std::vector *filled_regions) +{ + size_t ninsts = 0; - unsigned int row_steps () const { return m_row_steps; } - unsigned int column_steps () const { return m_column_steps; } + for (unsigned int i = 0; i < am.area_maps (); ++i) { - unsigned int area_maps () const - { - return (unsigned int) m_area_maps.size (); - } + db::AreaMap &am1 = am.area_map (i); + const db::AreaMap *am1_excl = 0; + if (exclude_rasterized) { + int ie = exclude_rasterized->index_for_p0 (am1.p0 ()); + if (ie >= 0) { + am1_excl = &exclude_rasterized->area_map (ie); + } + } - const db::AreaMap &area_map (unsigned int i) const - { - return m_area_maps [i]; - } + size_t nx = am1.nx (); + size_t ny = am1.ny (); - db::AreaMap &area_map (unsigned int i) - { - return m_area_maps [i]; - } + // Create the fill cell instances + for (size_t i = 0; i < nx; ++i) { -private: - std::vector m_area_maps; - db::Vector m_row_step, m_column_step; - unsigned int m_row_steps, m_column_steps; - db::Point m_origin; - db::Vector m_dim; -}; + for (size_t j = 0; j < ny; ) { + size_t jj = j + 1; + if (am1.get (i, j) == am1.pixel_area () && (!am1_excl || am1_excl->get (i, j) == 0)) { + + while (jj != ny && am1.get (i, jj) == am1.pixel_area () && (!am1_excl || am1_excl->get (i, jj) == 0)) { + ++jj; + } + + db::Vector p0 = (am1.p0 () - db::Point ()) - kernel_origin; + p0 += db::Vector (i * am1.d ().x (), j * am1.d ().y ()); + + db::CellInstArray array; + + // try to expand the array in x direction + size_t ii = i + 1; + for ( ; ii < nx; ++ii) { + bool all = true; + for (size_t k = j; k < jj && all; ++k) { + all = (am1.get (ii, k) == am1.pixel_area () && (!am1_excl || am1_excl->get (ii, k) == 0)); + } + if (all) { + for (size_t k = j; k < jj; ++k) { + // disable pixel, so we do not see it again in the following columns + am1.get (ii, k) = 0; + } + } else { + break; + } + } + + ninsts += (jj - j) * (ii - i); + + if (jj > j + 1 || ii > i + 1) { + array = db::CellInstArray (db::CellInst (fill_cell_index), db::Trans (p0), db::Vector (0, am1.d ().y ()), db::Vector (am1.d ().x (), 0), (unsigned long) (jj - j), (unsigned long) (ii - i)); + } else { + array = db::CellInstArray (db::CellInst (fill_cell_index), db::Trans (p0)); + } + + { + // In case we run this from a tiling processor we need to lock against multithread races + tl_assert (cell->layout () != 0); + tl::MutexLocker locker (&cell->layout ()->lock ()); + cell->insert (array); + } + + if (filled_regions) { + if (am1.d ().y () == am1.p ().y () && am1.d ().x () == am1.p ().x ()) { + db::Box fill_box (db::Point (), db::Point (am1.p ().x () * db::Coord (ii - i), am1.p ().y () * db::Coord (jj - j))); + filled_regions->push_back (db::Polygon (fill_box.enlarged (fill_margin).moved (kernel_origin + p0))); + } else { + db::Box fill_box (db::Point (), db::Point () + am1.p ()); + fill_box.enlarge (fill_margin); + for (size_t k = 0; k < jj - j; ++k) { + for (size_t l = 0; l < ii - i; ++l) { + filled_regions->push_back (db::Polygon (fill_box.moved (kernel_origin + p0 + db::Vector (am1.d ().x () * db::Coord (l), am1.d ().y () * db::Coord (k))))); + } + } + } + } + + } + + j = jj; + + } + + } + + } + + return ninsts; +} static bool fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type fill_cell_index, const db::Box &fc_bbox, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, - std::vector *remaining_parts, const db::Vector &fill_margin, const db::Box &glue_box) + std::vector *remaining_parts, const db::Vector &fill_margin, const db::Box &glue_box, const db::Region &exclude_area) { if (row_step.x () <= 0 || column_step.y () <= 0) { throw tl::Exception (tl::to_string (tr ("Invalid row or column step vectors in fill_region: row step must have a positive x component while column step must have a positive y component"))); @@ -197,156 +322,155 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f db::Vector kernel_origin (fc_bbox.left (), fc_bbox.bottom ()); - std::vector filled_regions; - db::EdgeProcessor ep; - - // under- and oversize the polygon to remove slivers that cannot be filled. db::Coord dx = fc_bbox.width () / 2 - 1, dy = fc_bbox.height () / 2 - 1; - std::vector fpa; - std::vector fpb; - fpa.push_back (fp0); + db::Region fr (fp0); + db::Box rasterized_area = fp0.box (); - ep.size (fpa, -dx, 0, fpb, 3 /*mode*/, false /*=don't resolve holes*/); - fpa.swap (fpb); - fpb.clear (); + std::unique_ptr exclude_rasterized; + bool has_exclude_area = false; - ep.size (fpa, dx, 0, fpb, 3 /*mode*/, false /*=don't resolve holes*/); - fpa.swap (fpb); - fpb.clear (); + if (! exclude_area.empty ()) { - ep.size (fpa, 0, -dy, fpb, 3 /*mode*/, false /*=don't resolve holes*/); - fpa.swap (fpb); - fpb.clear (); + auto it = exclude_area.begin_iter (); + it.first.confine_region (fp0.box ()); - ep.size (fpa, 0, dy, fpb, 3 /*mode*/, false /*=don't resolve holes*/); - fpa.swap (fpb); - fpb.clear (); + // over- and undersize the polygons to fill gaps that cannot be filled. + db::Region excluded (it.first, it.second); + excluded.set_merged_semantics (false); + excluded.size (dx, 0); + excluded.set_merged_semantics (true); + excluded.size (-dx, 0); + excluded.set_merged_semantics (false); + excluded.size (dy, 0); + excluded.set_merged_semantics (true); + excluded.size (-dy, 0); + excluded.merge (); - ep.simple_merge (fpa, fpb, false /*=don't resolve holes*/); + if (! excluded.empty ()) { - filled_regions.clear (); - bool any_fill = false; + has_exclude_area = true; - for (std::vector ::const_iterator fp = fpb.begin (); fp != fpb.end (); ++fp) { + if (enhanced_fill || remaining_parts != 0) { - if (fp->hull ().size () == 0) { - continue; - } + // In enhanced fill or if the remaining parts are requested, it is better to implement the + // exclude area by a boolean NOT + fr -= excluded; - // disable enhanced mode an obey the origin if the polygon is not entirely inside and not at the boundary of the glue box - bool ef = enhanced_fill; - if (ef && ! glue_box.empty () && ! fp->box ().enlarged (db::Vector (1, 1)).inside (glue_box)) { - ef = false; - } + } else { - // pick a heuristic "good" starting point in enhanced mode - // TODO: this is a pretty weak optimization. - db::Point o = origin; - if (ef) { - o = fp->hull () [0]; - } + // Otherwise use a second rasterizer for the exclude polygons that must have a zero pixel coverage for the + // pixel to be filled. - size_t ninsts = 0; + std::vector excluded_poly; + excluded_poly.reserve (excluded.count ()); + for (auto i = excluded.begin (); ! i.at_end (); ++i) { + excluded_poly.push_back (*i); + } + excluded.clear (); - GenericRasterizer am (*fp, row_step, column_step, o, fc_bbox.p2 () - fc_bbox.p1 ()); + exclude_rasterized.reset (new GenericRasterizer (excluded_poly, rasterized_area, row_step, column_step, origin, fc_bbox.p2 () - fc_bbox.p1 ())); - for (unsigned int i = 0; i < am.area_maps (); ++i) { + } - db::AreaMap &am1 = am.area_map (i); + } - size_t nx = am1.nx (); - size_t ny = am1.ny (); + } - // Create the fill cell instances - for (size_t i = 0; i < nx; ++i) { + std::vector filled_poly, filled_poly_uncleaned; - for (size_t j = 0; j < ny; ) { + // save the uncleaned polygons, so we subtract the filled parts to + // form the remaining parts + if (remaining_parts) { + filled_poly_uncleaned.reserve (fr.count ()); + for (auto i = fr.begin (); ! i.at_end (); ++i) { + filled_poly_uncleaned.push_back (*i); + } + } - size_t jj = j + 1; - if (am1.get (i, j) == am1.pixel_area ()) { + // under- and oversize the polygon to remove slivers that cannot be filled. + fr.set_merged_semantics (true); + fr.size (-dx, 0); + fr.set_merged_semantics (false); + fr.size (dx, 0); + fr.set_merged_semantics (true); + fr.size (0, -dy); + fr.set_merged_semantics (false); + fr.size (0, dy); + fr.set_merged_semantics (true); + fr.merge (); + + filled_poly.reserve (fr.count ()); + for (auto i = fr.begin (); ! i.at_end (); ++i) { + filled_poly.push_back (*i); + } - while (jj != ny && am1.get (i, jj) == am1.pixel_area ()) { - ++jj; - } + fr.clear (); - db::Vector p0 = (am1.p0 () - db::Point ()) - kernel_origin; - p0 += db::Vector (i * am1.d ().x (), j * am1.d ().y ()); + std::vector filled_regions; + bool any_fill = false; - db::CellInstArray array; + if (filled_poly.empty ()) { - // try to expand the array in x direction - size_t ii = i + 1; - for ( ; ii < nx; ++ii) { - bool all = true; - for (size_t k = j; k < jj && all; ++k) { - all = am1.get (ii, k) == am1.pixel_area (); - } - if (all) { - for (size_t k = j; k < jj; ++k) { - // disable pixel, so we do not see it again in the following columns - am1.get (ii, k) = 0; - } - } else { - break; - } - } + // not need to do anything - ninsts += (jj - j) * (ii - i); + } else if (exclude_rasterized.get ()) { - if (jj > j + 1 || ii > i + 1) { - array = db::CellInstArray (db::CellInst (fill_cell_index), db::Trans (p0), db::Vector (0, am1.d ().y ()), db::Vector (am1.d ().x (), 0), (unsigned long) (jj - j), (unsigned long) (ii - i)); - } else { - array = db::CellInstArray (db::CellInst (fill_cell_index), db::Trans (p0)); - } + tl_assert (remaining_parts == 0); + GenericRasterizer am (filled_poly, rasterized_area, row_step, column_step, origin, fc_bbox.p2 () - fc_bbox.p1 ()); - { - // In case we run this from a tiling processor we need to lock against multithread races - tl_assert (cell->layout () != 0); - tl::MutexLocker locker (&cell->layout ()->lock ()); - cell->insert (array); - } + size_t ninsts = create_instances (am, cell, fill_cell_index, kernel_origin, fill_margin, exclude_rasterized.get (), 0); + if (ninsts > 0) { + any_fill = true; + } - if (remaining_parts) { - if (am1.d ().y () == am1.p ().y () && am1.d ().x () == am1.p ().x ()) { - db::Box fill_box (db::Point (), db::Point (am1.p ().x () * db::Coord (ii - i), am1.p ().y () * db::Coord (jj - j))); - filled_regions.push_back (db::Polygon (fill_box.enlarged (fill_margin).moved (kernel_origin + p0))); - } else { - db::Box fill_box (db::Point (), db::Point () + am1.p ()); - fill_box.enlarge (fill_margin); - for (size_t k = 0; k < jj - j; ++k) { - for (size_t l = 0; l < ii - i; ++l) { - filled_regions.push_back (db::Polygon (fill_box.moved (kernel_origin + p0 + db::Vector (am1.d ().x () * db::Coord (l), am1.d ().y () * db::Coord (k))))); - } - } - } - } + if (tl::verbosity () >= 30 && ninsts > 0) { + tl::info << "Part " << fp0.to_string (); + tl::info << "Created " << ninsts << " instances"; + } - any_fill = true; + } else { - } + for (auto fp = filled_poly.begin (); fp != filled_poly.end (); ++fp) { - j = jj; + if (fp->is_empty ()) { + continue; + } - } + // disable enhanced mode an obey the origin if the polygon is not entirely inside and not at the boundary of the glue box + bool ef = enhanced_fill; + if (ef && ! glue_box.empty () && ! fp->box ().enlarged (db::Vector (1, 1)).inside (glue_box)) { + ef = false; + } + // pick a heuristic "good" starting point in enhanced mode + // TODO: this is a pretty weak optimization. + db::Point o = origin; + if (ef) { + o = fp->hull () [0]; } - } + GenericRasterizer am (*fp, rasterized_area, row_step, column_step, o, fc_bbox.p2 () - fc_bbox.p1 ()); + + size_t ninsts = create_instances (am, cell, fill_cell_index, kernel_origin, fill_margin, 0, remaining_parts ? &filled_regions : 0); + if (ninsts > 0) { + any_fill = true; + } + + if (tl::verbosity () >= 30 && ninsts > 0) { + tl::info << "Part " << fp->to_string (); + tl::info << "Created " << ninsts << " instances"; + } - if (tl::verbosity () >= 30 && ninsts > 0) { - tl::info << "Part " << fp->to_string (); - tl::info << "Created " << ninsts << " instances"; } } - if (any_fill) { + if (any_fill || has_exclude_area) { if (remaining_parts) { - std::vector fp1; - fp1.push_back (fp0); - ep.boolean (fp1, filled_regions, *remaining_parts, db::BooleanOp::ANotB, false /*=don't resolve holes*/); + db::EdgeProcessor ep; + ep.boolean (filled_poly_uncleaned, filled_regions, *remaining_parts, db::BooleanOp::ANotB, false /*=don't resolve holes*/); } return true; @@ -358,25 +482,25 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f DB_PUBLIC bool fill_region (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type fill_cell_index, const Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, - std::vector *remaining_parts, const db::Vector &fill_margin, const db::Box &glue_box) + std::vector *remaining_parts, const db::Vector &fill_margin, const db::Box &glue_box, const db::Region &exclude_area) { - return fill_polygon_impl (cell, fp0, fill_cell_index, fc_box, row_step, column_step, origin, enhanced_fill, remaining_parts, fill_margin, glue_box); + return fill_polygon_impl (cell, fp0, fill_cell_index, fc_box, row_step, column_step, origin, enhanced_fill, remaining_parts, fill_margin, glue_box, exclude_area); } DB_PUBLIC bool fill_region (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type fill_cell_index, const db::Box &fc_bbox, const db::Point &origin, bool enhanced_fill, - std::vector *remaining_parts, const db::Vector &fill_margin, const db::Box &glue_box) + std::vector *remaining_parts, const db::Vector &fill_margin, const db::Box &glue_box, const db::Region &exclude_area) { if (fc_bbox.empty () || fc_bbox.width () == 0 || fc_bbox.height () == 0) { throw tl::Exception (tl::to_string (tr ("Invalid fill cell footprint (empty or zero width/height)"))); } - return fill_polygon_impl (cell, fp0, fill_cell_index, fc_bbox, db::Vector (fc_bbox.width (), 0), db::Vector (0, fc_bbox.height ()), origin, enhanced_fill, remaining_parts, fill_margin, glue_box); + return fill_polygon_impl (cell, fp0, fill_cell_index, fc_bbox, db::Vector (fc_bbox.width (), 0), db::Vector (0, fc_bbox.height ()), origin, enhanced_fill, remaining_parts, fill_margin, glue_box, exclude_area); } static void fill_region_impl (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_bbox, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, - db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, int iteration, const db::Box &glue_box) + db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, int iteration, const db::Box &glue_box, const db::Region &exclude_area) { if (row_step.x () <= 0 || column_step.y () <= 0) { throw tl::Exception (tl::to_string (tr ("Invalid row or column step vectors in fill_region: row step must have a positive x component while column step must have a positive y component"))); @@ -403,7 +527,7 @@ fill_region_impl (db::Cell *cell, const db::Region &fr, db::cell_index_type fill tl::RelativeProgress progress (progress_title, n); for (db::Region::const_iterator p = fr.begin_merged (); !p.at_end (); ++p) { - if (! fill_polygon_impl (cell, *p, fill_cell_index, fc_bbox, row_step, column_step, origin, enhanced_fill, remaining_parts ? &rem_pp : 0, fill_margin, glue_box)) { + if (! fill_polygon_impl (cell, *p, fill_cell_index, fc_bbox, row_step, column_step, origin, enhanced_fill, remaining_parts ? &rem_pp : 0, fill_margin, glue_box, exclude_area)) { if (remaining_polygons) { rem_poly.push_back (*p); } @@ -433,27 +557,27 @@ fill_region_impl (db::Cell *cell, const db::Region &fr, db::cell_index_type fill DB_PUBLIC void fill_region (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_bbox, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, - db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { - fill_region_impl (cell, fr, fill_cell_index, fc_bbox, row_step, column_step, origin, enhanced_fill, remaining_parts, fill_margin, remaining_polygons, 0, glue_box); + fill_region_impl (cell, fr, fill_cell_index, fc_bbox, row_step, column_step, origin, enhanced_fill, remaining_parts, fill_margin, remaining_polygons, 0, glue_box, exclude_area); } DB_PUBLIC void fill_region (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_bbox, const db::Point &origin, bool enhanced_fill, - db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { if (fc_bbox.empty () || fc_bbox.width () == 0 || fc_bbox.height () == 0) { throw tl::Exception (tl::to_string (tr ("Invalid fill cell footprint (empty or zero width/height)"))); } fill_region_impl (cell, fr, fill_cell_index, fc_bbox, db::Vector (fc_bbox.width (), 0), db::Vector (0, fc_bbox.height ()), - origin, enhanced_fill, remaining_parts, fill_margin, remaining_polygons, 0, glue_box); + origin, enhanced_fill, remaining_parts, fill_margin, remaining_polygons, 0, glue_box, exclude_area); } DB_PUBLIC void fill_region_repeat (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, - const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { const db::Region *fill_region = &fr; @@ -467,7 +591,7 @@ fill_region_repeat (db::Cell *cell, const db::Region &fr, db::cell_index_type fi ++iteration; remaining.clear (); - fill_region_impl (cell, *fill_region, fill_cell_index, fc_box, row_step, column_step, db::Point (), true, &remaining, fill_margin, remaining_polygons, iteration, glue_box); + fill_region_impl (cell, *fill_region, fill_cell_index, fc_box, row_step, column_step, db::Point (), true, &remaining, fill_margin, remaining_polygons, iteration, glue_box, exclude_area); new_fill_region.swap (remaining); fill_region = &new_fill_region; diff --git a/src/db/db/dbFillTool.h b/src/db/db/dbFillTool.h index 67d23c8cb..7dab850c3 100644 --- a/src/db/db/dbFillTool.h +++ b/src/db/db/dbFillTool.h @@ -23,12 +23,12 @@ #include "dbTypes.h" #include "dbPolygon.h" +#include "dbRegion.h" namespace db { class Cell; -class Region; /** * @brief Creates a tiling pattern for a single polygon using a fill cell which is repeated periodically @@ -41,6 +41,7 @@ class Region; * @param column_step (some_versions) The column advance vector of the fill cell. By default this is (0, fc_bbox.height()) * @param origin Specifies the origin of the fill raster if enhanced_fill is false * @param enhanced_fill If set, the tiling offset will be optimized such that as much tiling cells fit into each polygon + * @param exclude_area The region which fill cells must not overlap * * Optional parameters: * @@ -79,12 +80,16 @@ class Region; */ DB_PUBLIC bool -fill_region (db::Cell *cell, const db::Polygon &fp, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Point &origin, bool enhanced_fill, - std::vector *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), const db::Box &glue_box = db::Box ()); +fill_region (db::Cell *cell, const db::Polygon &fp, db::cell_index_type fill_cell_index, const db::Box &fc_box, + const db::Point &origin, bool enhanced_fill, + std::vector *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), + const db::Box &glue_box = db::Box ()); DB_PUBLIC bool -fill_region (db::Cell *cell, const db::Polygon &fp, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, - std::vector *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), const db::Box &glue_box = db::Box ()); +fill_region (db::Cell *cell, const db::Polygon &fp, db::cell_index_type fill_cell_index, const db::Box &fc_box, + const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, + std::vector *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), + const db::Box &glue_box = db::Box (), const db::Region &exclude_area = db::Region ()); /** @@ -98,12 +103,16 @@ fill_region (db::Cell *cell, const db::Polygon &fp, db::cell_index_type fill_cel */ DB_PUBLIC void -fill_region (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Point &origin, bool enhanced_fill, - db::Region *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), db::Region *remaining_polygons = 0, const db::Box &glue_box = db::Box ()); +fill_region (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_box, + const db::Point &origin, bool enhanced_fill, + db::Region *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), db::Region *remaining_polygons = 0, + const db::Box &glue_box = db::Box (), const db::Region &exclude_area = db::Region ()); DB_PUBLIC void -fill_region (db::Cell *cell, const db::Region &fp, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, - db::Region *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), db::Region *remaining_polygons = 0, const db::Box &glue_box = db::Box ()); +fill_region (db::Cell *cell, const db::Region &fp, db::cell_index_type fill_cell_index, const db::Box &fc_box, + const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, + db::Region *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), db::Region *remaining_polygons = 0, + const db::Box &glue_box = db::Box (), const db::Region &exclude_area = db::Region ()); /** * @brief An iterative version for enhanced fill @@ -118,6 +127,7 @@ fill_region (db::Cell *cell, const db::Region &fp, db::cell_index_type fill_cell DB_PUBLIC void fill_region_repeat (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, - const db::Vector &fill_margin, db::Region *remaining_polygons = 0, const db::Box &glue_box = db::Box ()); + const db::Vector &fill_margin, db::Region *remaining_polygons = 0, + const db::Box &glue_box = db::Box (), const db::Region &exclude_area = db::Region ()); } diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index 1cc992459..bf2a84e0e 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -1780,7 +1780,7 @@ Layout::force_update () // NOTE: the assumption is that either one thread is writing or // multiple threads are reading. Hence, we do not need to lock hier_dirty() or bboxes_dirty(). // We still do double checking as another thread might do the update as well. - if (! hier_dirty () && ! bboxes_dirty ()) { + if (! update_needed ()) { return; } @@ -1829,10 +1829,16 @@ Layout::update () const } } +bool +Layout::update_needed () const +{ + return hier_dirty () || bboxes_dirty (); +} + void Layout::do_update () { - if (! hier_dirty () && ! bboxes_dirty ()) { + if (! update_needed ()) { return; } diff --git a/src/db/db/dbLayout.h b/src/db/db/dbLayout.h index 02b98a593..4c0d0078a 100644 --- a/src/db/db/dbLayout.h +++ b/src/db/db/dbLayout.h @@ -1619,6 +1619,14 @@ class DB_PUBLIC Layout */ top_down_const_iterator end_top_cells () const; + /** + * @brief Gets a value indicating whether an update is needed + * + * If this value is false, update or force_update will not + * do anything. + */ + bool update_needed () const; + /** * @brief Provide a const version of the update method * diff --git a/src/db/db/dbMeasureEval.cc b/src/db/db/dbMeasureEval.cc index b85291022..b2a29c10b 100644 --- a/src/db/db/dbMeasureEval.cc +++ b/src/db/db/dbMeasureEval.cc @@ -511,7 +511,7 @@ MeasureNetEval::put_func (const tl::Variant &name, const tl::Variant &value) con MeasureNetEval::AreaAndPerimeter MeasureNetEval::compute_area_and_perimeter (int layer_index) const { - if (layer_index < 0 || layer_index >= (unsigned int) m_layers.size ()) { + if (layer_index < 0 || layer_index >= (int) m_layers.size ()) { return AreaAndPerimeter (); } diff --git a/src/db/db/dbPolygon.h b/src/db/db/dbPolygon.h index b56a36271..a75a41564 100644 --- a/src/db/db/dbPolygon.h +++ b/src/db/db/dbPolygon.h @@ -1512,16 +1512,17 @@ class DB_PUBLIC_TEMPLATE polygon * @param tr The transformation to apply on assignment * @param compress True, if the contours shall be compressed * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - polygon (const db::polygon &p, const T &tr, bool compress = default_compression (), bool remove_reflected = false) + polygon (const db::polygon &p, const T &tr, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { // create an entry for the hull contour m_bbox = box_type (tr (p.box ().p1 ()), tr (p.box ().p2 ())); m_ctrs.resize (p.holes () + 1); - m_ctrs [0].assign (p.begin_hull (), p.end_hull (), tr, false, compress, true /*normalize*/, remove_reflected); + m_ctrs [0].assign (p.begin_hull (), p.end_hull (), tr, false, compress, normalize, remove_reflected); for (unsigned int i = 0; i < m_ctrs.size () - 1; ++i) { - m_ctrs [i + 1].assign (p.begin_hole (i), p.end_hole (i), tr, true, compress, true /*normalize*/, remove_reflected); + m_ctrs [i + 1].assign (p.begin_hole (i), p.end_hole (i), tr, true, compress, normalize, remove_reflected); } } @@ -1533,16 +1534,16 @@ class DB_PUBLIC_TEMPLATE polygon * @param remove_reflected True, if reflecting spikes shall be removed on compression */ template - explicit polygon (const db::polygon &p, bool compress = default_compression (), bool remove_reflected = false) + explicit polygon (const db::polygon &p, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { db::point_coord_converter tr; // create an entry for the hull contour m_bbox = box_type (tr (p.box ().p1 ()), tr (p.box ().p2 ())); m_ctrs.resize (p.holes () + 1); - m_ctrs [0].assign (p.begin_hull (), p.end_hull (), tr, false, compress, true /*normalize*/, remove_reflected); + m_ctrs [0].assign (p.begin_hull (), p.end_hull (), tr, false, compress, normalize, remove_reflected); for (unsigned int i = 0; i < m_ctrs.size () - 1; ++i) { - m_ctrs [i + 1].assign (p.begin_hole (i), p.end_hole (i), tr, true, compress, true /*normalize*/, remove_reflected); + m_ctrs [i + 1].assign (p.begin_hole (i), p.end_hole (i), tr, true, compress, normalize, remove_reflected); } } @@ -2072,11 +2073,12 @@ class DB_PUBLIC_TEMPLATE polygon * @param end The end of the sequence of points for the contour * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - void assign_hull (I start, I end, bool compress = default_compression (), bool remove_reflected = false) + void assign_hull (I start, I end, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { - m_ctrs [0].assign (start, end, false, compress, true /*normalize*/, remove_reflected); + m_ctrs [0].assign (start, end, false, compress, normalize, remove_reflected); m_bbox = m_ctrs [0].bbox (); } @@ -2089,14 +2091,15 @@ class DB_PUBLIC_TEMPLATE polygon * so it is oriented properly. * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized * * @param start The start of the sequence of points for the contour * @param end The end of the sequence of points for the contour */ template - void assign_hull (I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false) + void assign_hull (I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { - m_ctrs [0].assign (start, end, op, false, compress, true /*normalize*/, remove_reflected); + m_ctrs [0].assign (start, end, op, false, compress, normalize, remove_reflected); m_bbox = m_ctrs [0].bbox (); } @@ -2128,11 +2131,12 @@ class DB_PUBLIC_TEMPLATE polygon * @param end The end of the sequence of points for the contour * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - void assign_hole (unsigned int h, I start, I end, bool compress = default_compression (), bool remove_reflected = false) + void assign_hole (unsigned int h, I start, I end, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { - m_ctrs [h + 1].assign (start, end, true, compress, true /*normalize*/, remove_reflected); + m_ctrs [h + 1].assign (start, end, true, compress, normalize, remove_reflected); } /** @@ -2144,14 +2148,15 @@ class DB_PUBLIC_TEMPLATE polygon * so it is oriented properly. * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized * * @param start The start of the sequence of points for the contour * @param end The end of the sequence of points for the contour */ template - void assign_hole (unsigned int h, I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false) + void assign_hole (unsigned int h, I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { - m_ctrs [h + 1].assign (start, end, op, true, compress, true /*normalize*/, remove_reflected); + m_ctrs [h + 1].assign (start, end, op, true, compress, normalize, remove_reflected); } /** @@ -2183,11 +2188,12 @@ class DB_PUBLIC_TEMPLATE polygon * @param end The end of the sequence of points for the contour * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - void insert_hole (I start, I end, bool compress = default_compression (), bool remove_reflected = false) + void insert_hole (I start, I end, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { - insert_hole (start, end, db::unit_trans (), compress, remove_reflected); + insert_hole (start, end, db::unit_trans (), compress, remove_reflected, normalize); } /** @@ -2204,13 +2210,14 @@ class DB_PUBLIC_TEMPLATE polygon * @param end The end of the sequence of points for the contour * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - void insert_hole (I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false) + void insert_hole (I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { // add the hole contour_type &h = add_hole (); - h.assign (start, end, op, true, compress, true /*normalize*/, remove_reflected); + h.assign (start, end, op, true, compress, normalize, remove_reflected); } /** @@ -2637,13 +2644,14 @@ class DB_PUBLIC_TEMPLATE simple_polygon * @param tr The transformation to apply * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - simple_polygon (const db::simple_polygon &p, const T &tr, bool compress = default_compression (), bool remove_reflected = false) + simple_polygon (const db::simple_polygon &p, const T &tr, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { // create an entry for the hull contour m_bbox = box_type (tr (p.box ().p1 ()), tr (p.box ().p2 ())); - m_hull.assign (p.begin_hull (), p.end_hull (), tr, false, compress, true /*normalize*/, remove_reflected); + m_hull.assign (p.begin_hull (), p.end_hull (), tr, false, compress, normalize, remove_reflected); } /** @@ -2652,15 +2660,16 @@ class DB_PUBLIC_TEMPLATE simple_polygon * @param p The source polygon * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - explicit simple_polygon (const db::simple_polygon &p, bool compress = default_compression (), bool remove_reflected = false) + explicit simple_polygon (const db::simple_polygon &p, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { db::point_coord_converter tr; // create an entry for the hull contour m_bbox = box_type (tr (p.box ().p1 ()), tr (p.box ().p2 ())); - m_hull.assign (p.begin_hull (), p.end_hull (), tr, false, compress, true /*normalize*/, remove_reflected); + m_hull.assign (p.begin_hull (), p.end_hull (), tr, false, compress, normalize, remove_reflected); } /** @@ -2992,11 +3001,12 @@ class DB_PUBLIC_TEMPLATE simple_polygon * @param end The end of the sequence of points for the contour * @param compress true, if the sequence shall be compressed (colinear segments joined) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - void assign_hull (I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false) + void assign_hull (I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { - m_hull.assign (start, end, op, false, compress, true /*normalize*/, remove_reflected); + m_hull.assign (start, end, op, false, compress, normalize, remove_reflected); m_bbox = m_hull.bbox (); } diff --git a/src/db/db/dbShapes.cc b/src/db/db/dbShapes.cc index ff2c1bfff..8d396730b 100644 --- a/src/db/db/dbShapes.cc +++ b/src/db/db/dbShapes.cc @@ -1441,8 +1441,10 @@ Shapes::replace_member_with_props (typename db::object_tag tag, const shape_ if (! layout ()) { if (needs_translate (tag)) { + return reinsert_member_with_props (tag, ref, sh); - } else { + + } else if (! ref.has_prop_id ()) { // simple replace case @@ -1459,7 +1461,21 @@ Shapes::replace_member_with_props (typename db::object_tag tag, const shape_ db::layer_op::queue_or_append (manager (), this, true /*insert*/, sh); } - return ref; + } else { + + if (manager () && manager ()->transacting ()) { + check_is_editable_for_undo_redo (); + db::layer_op, db::stable_layer_tag>::queue_or_append (manager (), this, false /*not insert*/, *ref.basic_ptr (typename db::object_with_properties::tag ())); + } + + invalidate_state (); // HINT: must come before the change is done! + + db::object_with_properties swp (sh, ref.prop_id ()); + get_layer, db::stable_layer_tag> ().replace (ref.basic_iter (typename db::object_with_properties::tag ()), swp); + + if (manager () && manager ()->transacting ()) { + db::layer_op, db::stable_layer_tag>::queue_or_append (manager (), this, true /*insert*/, swp); + } } @@ -1514,9 +1530,9 @@ Shapes::replace_member_with_props (typename db::object_tag tag, const shape_ } - return ref; - } + + return ref; } // explicit instantiations diff --git a/src/db/db/dbWriter.cc b/src/db/db/dbWriter.cc index bab0f58dd..9cf4cfb9a 100644 --- a/src/db/db/dbWriter.cc +++ b/src/db/db/dbWriter.cc @@ -58,7 +58,7 @@ Writer::write (db::Layout &layout, tl::OutputStream &stream) { tl::SelfTimer timer (tl::verbosity () >= 21, tl::to_string (tr ("Writing file: ")) + stream.path ()); - if (layout.under_construction ()) { + if (layout.under_construction () && layout.update_needed ()) { tl::warn << tl::to_string (tr ("Cannot properly write a layout that is under construction - forcing update.")); layout.force_update (); } diff --git a/src/db/db/gsiDeclDbCell.cc b/src/db/db/gsiDeclDbCell.cc index 6bcb72581..7138418e7 100644 --- a/src/db/db/gsiDeclDbCell.cc +++ b/src/db/db/gsiDeclDbCell.cc @@ -1510,23 +1510,23 @@ static void move_tree_shapes3 (db::Cell *cell, db::Cell &source_cell, const db:: static void fill_region (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Point *origin, - db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { - db::fill_region (cell, fr, fill_cell_index, fc_box, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box); + db::fill_region (cell, fr, fill_cell_index, fc_box, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box, exclude_area); } static void fill_region_skew (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, const db::Point *origin, - db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { - db::fill_region (cell, fr, fill_cell_index, fc_box, row_step, column_step, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box); + db::fill_region (cell, fr, fill_cell_index, fc_box, row_step, column_step, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box, exclude_area); } static void fill_region_multi (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, - const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { - db::fill_region_repeat (cell, fr, fill_cell_index, fc_box, row_step, column_step, fill_margin, remaining_polygons, glue_box); + db::fill_region_repeat (cell, fr, fill_cell_index, fc_box, row_step, column_step, fill_margin, remaining_polygons, glue_box, exclude_area); } static db::Instance cell_inst_dtransform_simple (db::Cell *cell, const db::Instance &inst, const db::DTrans &t) @@ -2215,6 +2215,7 @@ Class decl_Cell ("db", "Cell", gsi::arg ("fill_margin", db::Vector ()), gsi::arg ("remaining_polygons", (db::Region *)0, "nil"), gsi::arg ("glue_box", db::Box ()), + gsi::arg ("exclude_area", db::Region (), "empty"), "@brief Fills the given region with cells of the given type (extended version)\n" "@param region The region to fill\n" "@param fill_cell_index The fill cell to place\n" @@ -2224,6 +2225,7 @@ Class decl_Cell ("db", "Cell", "@param fill_margin See explanation below\n" "@param remaining_polygons See explanation below\n" "@param glue_box Guarantees fill cell compatibility to neighbor regions in enhanced mode\n" + "@param exclude_area A region that defines the areas which are not be filled\n" "\n" "This method creates a regular pattern of fill cells to cover the interior of the given region as far as possible. " "This process is also known as tiling. This implementation supports rectangular (not necessarily square) tile cells. " @@ -2236,6 +2238,7 @@ Class decl_Cell ("db", "Cell", "\n" "The implementation will basically try to find a repetition pattern of the tile cell's footprint " "and produce instances which fit entirely into the fill region.\n" + "If an exclude area is given, the fill cells also must not overlap that region.\n" "\n" "There is also a version available which offers skew step vectors as a generalization of the orthogonal ones.\n" "\n" @@ -2246,8 +2249,11 @@ Class decl_Cell ("db", "Cell", "If the 'remaining_polygons' argument is non-nil, the corresponding region will receive all polygons from the input region " "which could not be filled and where there is no chance of filling because not a single tile will fit into them.\n" "\n" - "'remaining_parts' and 'remaining_polygons' can be identical with the input. In that case the input will be overwritten with " + "'remaining_parts' and 'remaining_polygons' can point to the same Region object.\n" + "They can also be identical with the input. In that case the input will be overwritten with " "the respective output. Otherwise, the respective polygons are added to these regions.\n" + "'remaining_polygons' is not used if 'exclude_area' is present and non-empty. In that case, the\n" + "original polygons, which cannot be filled at all, are copied to 'remaining_parts'.\n" "\n" "This allows setting up a more elaborate fill scheme using multiple iterations and local origin-optimization ('origin' is nil):\n" "\n" @@ -2260,7 +2266,7 @@ Class decl_Cell ("db", "Cell", "fill_margin = RBA::Point::new(0, 0) # x/y distance between tile cells with different origin\n" "\n" "# Iteration: fill a region and fill the remaining parts as long as there is anything left.\n" - "# Polygons not worth being considered further are dropped (last argument is nil).\n" + "# Polygons not worth being considered further are dropped ('remaining_polygons' argument is nil).\n" "while !r.is_empty?\n" " c.fill_region(r, fc_index, fc_box, nil, r, fill_margin, nil)\n" "end\n" @@ -2275,7 +2281,7 @@ Class decl_Cell ("db", "Cell", "at the raster implied by origin at the glue box border and beyond. To ensure fill cell compatibility inside the tiling processor, it is sufficient to use the tile " "box as the glue box.\n" "\n" - "This method has been introduced in version 0.23 and enhanced in version 0.27.\n" + "This method has been introduced in version 0.23 and enhanced in version 0.27. The 'exclude_area' argument has been added in version 0.30.4.\n" ) + gsi::method_ext ("fill_region", &fill_region_skew, gsi::arg ("region"), gsi::arg ("fill_cell_index"), @@ -2287,16 +2293,18 @@ Class decl_Cell ("db", "Cell", gsi::arg ("fill_margin", db::Vector ()), gsi::arg ("remaining_polygons", (db::Region *)0, "nil"), gsi::arg ("glue_box", db::Box ()), + gsi::arg ("exclude_area", db::Region (), "empty"), "@brief Fills the given region with cells of the given type (skew step version)\n" "@param region The region to fill\n" "@param fill_cell_index The fill cell to place\n" - "@param fc_bbox The fill cell's box to place\n" + "@param fc_bbox The fill cell's box, defining the box that needs to be inside the fill region\n" "@param row_step The 'rows' step vector\n" "@param column_step The 'columns' step vector\n" "@param origin The global origin of the fill pattern or nil to allow local (per-polygon) optimization\n" "@param remaining_parts See explanation in other version\n" "@param fill_margin See explanation in other version\n" "@param remaining_polygons See explanation in other version\n" + "@param exclude_area A region that defines the areas which are not be filled\n" "\n" "This version is similar to the version providing an orthogonal fill, but it offers more generic stepping of the fill cell.\n" "The step pattern is defined by an origin and two vectors (row_step and column_step) which span the axes of the fill cell pattern.\n" @@ -2305,7 +2313,7 @@ Class decl_Cell ("db", "Cell", "be overlapping and there can be space between the fill box instances. Fill boxes are placed where they fit entirely into a polygon of the region. " "The fill boxes lower left corner is the reference for the fill pattern and aligns with the origin if given.\n" "\n" - "This variant has been introduced in version 0.27.\n" + "This variant has been introduced in version 0.27. The 'exclude_area' argument has been added in version 0.30.4.\n" ) + gsi::method_ext ("fill_region_multi", &fill_region_multi, gsi::arg ("region"), gsi::arg ("fill_cell_index"), @@ -2315,6 +2323,7 @@ Class decl_Cell ("db", "Cell", gsi::arg ("fill_margin", db::Vector ()), gsi::arg ("remaining_polygons", (db::Region *)0, "nil"), gsi::arg ("glue_box", db::Box ()), + gsi::arg ("exclude_area", db::Region (), "empty"), "@brief Fills the given region with cells of the given type in enhanced mode with iterations\n" "This version operates like \\fill_region, but repeats the fill generation until no further fill cells can be placed. " "As the fill pattern origin changes between the iterations, narrow regions can be filled which cannot with a fixed fill pattern origin. " @@ -2323,7 +2332,7 @@ Class decl_Cell ("db", "Cell", "\n" "The origin is ignored unless a glue box is given. See \\fill_region for a description of this concept.\n" "\n" - "This method has been introduced in version 0.27.\n" + "This method has been introduced in version 0.27. The 'exclude_area' argument has been added in version 0.30.4.\n" ) + gsi::method_ext ("begin_shapes_rec", &begin_shapes_rec, gsi::arg ("layer"), "@brief Delivers a recursive shape iterator for the shapes below the cell on the given layer\n" diff --git a/src/db/db/gsiDeclDbLayout.cc b/src/db/db/gsiDeclDbLayout.cc index 647dbd921..c222a74ac 100644 --- a/src/db/db/gsiDeclDbLayout.cc +++ b/src/db/db/gsiDeclDbLayout.cc @@ -1758,6 +1758,13 @@ Class decl_Layout ("db", "Layout", "is ongoing or the layout is brought into invalid state by\n" "\"start_changes\".\n" ) + + gsi::method ("update_needed", &db::Layout::update_needed, + "@brief Gets a value indicating whether the Layout object needs an update\n" + "If this method returns false, \\update will not do anything. This is useful to force an update at " + "specific times during 'under_construction' conditions.\n" + "\n" + "This method has been introduced in version 0.30.4." + ) + gsi::method ("update", (void (db::Layout::*) ()) &db::Layout::force_update, "@brief Updates the internals of the layout\n" "This method updates the internal state of the layout. Usually this is done automatically\n" diff --git a/src/db/db/gsiDeclDbPolygon.cc b/src/db/db/gsiDeclDbPolygon.cc index a5cb6f994..850887318 100644 --- a/src/db/db/gsiDeclDbPolygon.cc +++ b/src/db/db/gsiDeclDbPolygon.cc @@ -868,12 +868,12 @@ static db::SimplePolygon transformed_icplx_sp (const db::SimplePolygon *p, const static db::SimplePolygon *spolygon_from_dspolygon (const db::DSimplePolygon &p) { - return new db::SimplePolygon (p, false); + return new db::SimplePolygon (p, false, false /*don't remove reflected*/, false /*no normalize*/); } static db::DSimplePolygon spolygon_to_dspolygon (const db::SimplePolygon *p, double dbu) { - return db::DSimplePolygon (*p * dbu, false); + return db::DSimplePolygon (*p, db::CplxTrans (dbu), false, false /*don't remove reflected*/, false /*no normalize*/); } Class decl_SimplePolygon ("db", "SimplePolygon", @@ -1031,12 +1031,12 @@ Class decl_SimplePolygonWithProperties (decl_Si static db::DSimplePolygon *dspolygon_from_ispolygon (const db::SimplePolygon &p) { - return new db::DSimplePolygon (p, false); + return new db::DSimplePolygon (p, false, false /*don't remove reflected*/, false /*no normalize*/); } static db::SimplePolygon dspolygon_to_spolygon (const db::DSimplePolygon *p, double dbu) { - return db::SimplePolygon (*p * (1.0 / dbu), false); + return db::SimplePolygon (*p, db::VCplxTrans (1.0 / dbu), false, false /*don't remove reflected*/, false /*no normalize*/); } static db::SimplePolygon transformed_vplx_sp (const db::DSimplePolygon *p, const db::VCplxTrans &t) @@ -2036,12 +2036,12 @@ static db::Polygon minkowski_sum_pc (const db::Polygon *p, const std::vector decl_PolygonWithProperties (decl_Polygon, "db", static db::DPolygon *dpolygon_from_ipolygon (const db::Polygon &p) { - return new db::DPolygon (p, false); + return new db::DPolygon (p, false, false /*don't remove reflected*/, false /*no normalize*/); } static db::Polygon dpolygon_to_polygon (const db::DPolygon *p, double dbu) { - return db::Polygon (*p * (1.0 / dbu), false); + return db::Polygon (*p, db::VCplxTrans (1.0 / dbu), false, false /*don't remove reflected*/, false /*no normalize*/); } static db::Polygon transformed_vcplx_dp (const db::DPolygon *p, const db::VCplxTrans &t) diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 3e72eef94..386fcfee4 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -1309,23 +1309,23 @@ tl::Variant complex_op (db::Region *region, db::CompoundRegionOperationNode *nod static void fill_region (const db::Region *fr, db::Cell *cell, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Point *origin, - db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { - db::fill_region (cell, *fr, fill_cell_index, fc_box, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box); + db::fill_region (cell, *fr, fill_cell_index, fc_box, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box, exclude_area); } static void fill_region_skew (const db::Region *fr, db::Cell *cell, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, const db::Point *origin, - db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { - db::fill_region (cell, *fr, fill_cell_index, fc_box, row_step, column_step, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box); + db::fill_region (cell, *fr, fill_cell_index, fc_box, row_step, column_step, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box, exclude_area); } static void fill_region_multi (const db::Region *fr, db::Cell *cell, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, - const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { - db::fill_region_repeat (cell, *fr, fill_cell_index, fc_box, row_step, column_step, fill_margin, remaining_polygons, glue_box); + db::fill_region_repeat (cell, *fr, fill_cell_index, fc_box, row_step, column_step, fill_margin, remaining_polygons, glue_box, exclude_area); } static db::Region @@ -4340,41 +4340,44 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", gsi::arg ("fill_margin", db::Vector ()), gsi::arg ("remaining_polygons", (db::Region *)0, "nil"), gsi::arg ("glue_box", db::Box ()), + gsi::arg ("exclude_area", db::Region (), "empty"), "@brief A mapping of \\Cell#fill_region to the Region class\n" "\n" "This method is equivalent to \\Cell#fill_region, but is based on Region (with the cell being the first parameter).\n" "\n" - "This method has been introduced in version 0.27.\n" + "This method has been introduced in version 0.27. The 'exclude_area' argument has been added in version 0.30.4.\n" ) + gsi::method_ext ("fill", &fill_region_skew, gsi::arg ("in_cell"), gsi::arg ("fill_cell_index"), - gsi::arg ("fc_origin"), + gsi::arg ("fc_bbox"), gsi::arg ("row_step"), gsi::arg ("column_step"), gsi::arg ("origin", &default_origin, "(0, 0)"), gsi::arg ("remaining_parts", (db::Region *)0, "nil"), gsi::arg ("fill_margin", db::Vector ()), gsi::arg ("remaining_polygons", (db::Region *)0, "nil"), - gsi::arg ("glue_box", db::Box ()), + gsi::arg ("glue_box", db::Box ()), + gsi::arg ("exclude_area", db::Region (), "empty"), "@brief A mapping of \\Cell#fill_region to the Region class\n" "\n" "This method is equivalent to \\Cell#fill_region, but is based on Region (with the cell being the first parameter).\n" "\n" - "This method has been introduced in version 0.27.\n" + "This method has been introduced in version 0.27. The 'exclude_area' argument has been added in version 0.30.4.\n" ) + gsi::method_ext ("fill_multi", &fill_region_multi, gsi::arg ("in_cell"), gsi::arg ("fill_cell_index"), - gsi::arg ("fc_origin"), + gsi::arg ("fc_bbox"), gsi::arg ("row_step"), gsi::arg ("column_step"), gsi::arg ("fill_margin", db::Vector ()), gsi::arg ("remaining_polygons", (db::Region *)0, "nil"), gsi::arg ("glue_box", db::Box ()), + gsi::arg ("exclude_area", db::Region (), "empty"), "@brief A mapping of \\Cell#fill_region to the Region class\n" "\n" "This method is equivalent to \\Cell#fill_region, but is based on Region (with the cell being the first parameter).\n" "\n" - "This method has been introduced in version 0.27.\n" + "This method has been introduced in version 0.27. The 'exclude_area' argument has been added in version 0.30.4.\n" ) + gsi::method_ext ("nets", &nets, gsi::arg ("extracted"), gsi::arg ("net_prop_name", tl::Variant (), "nil"), gsi::arg ("net_filter", (const std::vector *) (0), "nil"), "@brief Pulls the net shapes from a LayoutToNetlist database\n" diff --git a/src/db/db/gsiDeclDbVia.cc b/src/db/db/gsiDeclDbVia.cc index 3b446bbe6..8e167435c 100644 --- a/src/db/db/gsiDeclDbVia.cc +++ b/src/db/db/gsiDeclDbVia.cc @@ -156,7 +156,7 @@ Class decl_dbViaType ("db", "ViaType", "@brief If non-zero, the top layer's dimensions will be rounded to this grid.\n" ), "@brief Describes a via type\n" - "These objects are used by PCellDeclaration#via_types to specify the via types a " + "These objects are used by \\PCellDeclaration#via_types to specify the via types a " "via PCell is able to provide.\n" "\n" "The basic parameters of a via type are bottom and top layers (the layers that are " diff --git a/src/db/unit_tests/dbFillToolTests.cc b/src/db/unit_tests/dbFillToolTests.cc index 4ce7b53bb..e36af2eb6 100644 --- a/src/db/unit_tests/dbFillToolTests.cc +++ b/src/db/unit_tests/dbFillToolTests.cc @@ -377,3 +377,98 @@ TEST(6) CHECKPOINT(); db::compare_layouts (_this, ly, tl::testdata () + "/algo/fill_tool_au6.oas", db::WriteOAS); } + +// exclude_area +TEST(7) +{ + db::Layout ly; + { + std::string fn (tl::testdata ()); + fn += "/algo/fill_tool7.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly); + } + + db::cell_index_type fill_cell = ly.cell_by_name ("FILL_CELL").second; + db::cell_index_type top_cell = ly.cell_by_name ("TOP").second; + + unsigned int fill_layer = ly.get_layer (db::LayerProperties (1, 0)); + db::Region fill_region (db::RecursiveShapeIterator (ly, ly.cell (top_cell), fill_layer)); + + unsigned int excl_layer = ly.get_layer (db::LayerProperties (2, 0)); + db::Region excl_region (db::RecursiveShapeIterator (ly, ly.cell (top_cell), excl_layer)); + + db::Region remaining_polygons; + + db::Vector rs (2500, 0); + db::Vector cs (650, 2500); + db::Box fc_box = ly.cell (fill_cell).bbox (); + db::fill_region (&ly.cell (top_cell), fill_region, fill_cell, fc_box, rs, cs, db::Point (), false, &remaining_polygons, db::Vector (), 0, db::Box (), excl_region); + + unsigned int l100 = ly.insert_layer (db::LayerProperties (100, 0)); + remaining_polygons.insert_into (&ly, top_cell, l100); + + CHECKPOINT(); + db::compare_layouts (_this, ly, tl::testdata () + "/algo/fill_tool_au7.oas", db::WriteOAS); +} + +// exclude_area +TEST(8) +{ + db::Layout ly; + { + std::string fn (tl::testdata ()); + fn += "/algo/fill_tool8.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly); + } + + db::cell_index_type fill_cell = ly.cell_by_name ("FILL_CELL").second; + db::cell_index_type top_cell = ly.cell_by_name ("TOP").second; + + unsigned int fill_layer = ly.get_layer (db::LayerProperties (1, 0)); + db::Region fill_region (db::RecursiveShapeIterator (ly, ly.cell (top_cell), fill_layer)); + + unsigned int excl_layer = ly.get_layer (db::LayerProperties (2, 0)); + db::Region excl_region (db::RecursiveShapeIterator (ly, ly.cell (top_cell), excl_layer)); + + db::Vector rs (2500, 0); + db::Vector cs (650, 2500); + db::Box fc_box = ly.cell (fill_cell).bbox (); + db::fill_region (&ly.cell (top_cell), fill_region, fill_cell, fc_box, rs, cs, db::Point (), false, 0, db::Vector (), 0, db::Box (), excl_region); + + CHECKPOINT(); + db::compare_layouts (_this, ly, tl::testdata () + "/algo/fill_tool_au8.oas", db::WriteOAS); +} + +// exclude_area +TEST(9) +{ + db::Layout ly; + { + std::string fn (tl::testdata ()); + fn += "/algo/fill_tool9.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly); + } + + db::cell_index_type fill_cell = ly.cell_by_name ("FILL_CELL").second; + db::cell_index_type top_cell = ly.cell_by_name ("TOP").second; + + unsigned int fill_layer = ly.get_layer (db::LayerProperties (1, 0)); + db::Region fill_region (db::RecursiveShapeIterator (ly, ly.cell (top_cell), fill_layer)); + + unsigned int excl_layer = ly.get_layer (db::LayerProperties (2, 0)); + db::Region excl_region (db::RecursiveShapeIterator (ly, ly.cell (top_cell), excl_layer)); + + db::Vector rs (2500, 0); + db::Vector cs (650, 2500); + db::Box fc_box = ly.cell (fill_cell).bbox (); + db::fill_region (&ly.cell (top_cell), fill_region, fill_cell, fc_box, rs, cs, db::Point (), true, 0, db::Vector (), 0, db::Box (), excl_region); + + CHECKPOINT(); + db::compare_layouts (_this, ly, tl::testdata () + "/algo/fill_tool_au9.oas", db::WriteOAS); +} diff --git a/src/db/unit_tests/dbLayoutTests.cc b/src/db/unit_tests/dbLayoutTests.cc index e96cb5c28..00fbb2056 100644 --- a/src/db/unit_tests/dbLayoutTests.cc +++ b/src/db/unit_tests/dbLayoutTests.cc @@ -294,7 +294,9 @@ TEST(2) EXPECT_EQ (g.hier_generation_id (), size_t (3)); g.clear (); + EXPECT_EQ (g.update_needed (), true); g.update (); + EXPECT_EQ (g.update_needed (), false); el.reset (); EXPECT_EQ (g.hier_generation_id (), size_t (4)); @@ -387,7 +389,9 @@ TEST(3) EXPECT_EQ (el.hier_dirty, true); el.reset (); + EXPECT_EQ (g.update_needed (), true); g.update (); + EXPECT_EQ (g.update_needed (), false); top->shapes (0).insert (db::Box (0, 0, 10, 20)); top->shapes (1).insert (db::Box (0, 0, 10, 20)); diff --git a/src/db/unit_tests/dbShapesTests.cc b/src/db/unit_tests/dbShapesTests.cc index fd584449e..7fb53898c 100644 --- a/src/db/unit_tests/dbShapesTests.cc +++ b/src/db/unit_tests/dbShapesTests.cc @@ -2404,6 +2404,10 @@ TEST(12A) db::Cell &topcell = layout.cell (*layout.begin_top_down ()); + // standalone copy + db::Shapes copy (true); + copy = topcell.shapes (lindex); + db::Shapes::shape_iterator shape = topcell.shapes (lindex).begin (db::Shapes::shape_iterator::All); while (! shape.at_end ()) { topcell.shapes (lindex).replace (*shape, db::Box (shape->box ().transformed (db::Trans (1)))); @@ -2436,6 +2440,70 @@ TEST(12A) "box (-1050,150;-150,2150) #112\n" ); + shape = topcell.shapes (lindex).begin (db::Shapes::shape_iterator::All); + while (! shape.at_end ()) { + topcell.shapes (lindex).replace (*shape, db::Box (shape->box ().transformed (db::Trans (1)))); + ++shape; + } + + EXPECT_EQ (shapes_to_string (_this, topcell.shapes (lindex)), + "box (-2000,-1000;0,-100) #0\n" + "box (-2100,-1100;-100,-200) #0\n" + "box (-2150,-1050;-150,-150) #0\n" + "box (-2000,-1000;0,-100) #110\n" + "box (-2100,-1100;-100,-200) #111\n" + "box (-2150,-1050;-150,-150) #112\n" + ); + + // on standalone shapes + + shape = copy.begin (db::Shapes::shape_iterator::All); + while (! shape.at_end ()) { + copy.replace (*shape, db::Box (shape->box ().transformed (db::Trans (1)))); + ++shape; + } + + EXPECT_EQ (shapes_to_string (_this, copy), + "box (-1000,0;-100,2000) #0\n" + "box (-1100,100;-200,2100) #0\n" + "box (-1050,150;-150,2150) #0\n" + "box (-1000,0;-100,2000) #10\n" + "box (-1100,100;-200,2100) #11\n" + "box (-1050,150;-150,2150) #12\n" + ); + + shape = copy.begin (db::Shapes::shape_iterator::All); + while (! shape.at_end ()) { + if (shape->has_prop_id ()) { + copy.replace_prop_id (*shape, shape->prop_id () + 100); + } + ++shape; + } + + EXPECT_EQ (shapes_to_string (_this, copy), + "box (-1000,0;-100,2000) #0\n" + "box (-1100,100;-200,2100) #0\n" + "box (-1050,150;-150,2150) #0\n" + "box (-1000,0;-100,2000) #110\n" + "box (-1100,100;-200,2100) #111\n" + "box (-1050,150;-150,2150) #112\n" + ); + + shape = copy.begin (db::Shapes::shape_iterator::All); + while (! shape.at_end ()) { + copy.replace (*shape, db::Box (shape->box ().transformed (db::Trans (1)))); + ++shape; + } + + EXPECT_EQ (shapes_to_string (_this, copy), + "box (-2000,-1000;0,-100) #0\n" + "box (-2100,-1100;-100,-200) #0\n" + "box (-2150,-1050;-150,-150) #0\n" + "box (-2000,-1000;0,-100) #110\n" + "box (-2100,-1100;-100,-200) #111\n" + "box (-2150,-1050;-150,-150) #112\n" + ); + } } diff --git a/src/doc/doc/manual/create_path.xml b/src/doc/doc/manual/create_path.xml index 1cee916cb..e6763c061 100644 --- a/src/doc/doc/manual/create_path.xml +++ b/src/doc/doc/manual/create_path.xml @@ -10,16 +10,16 @@

Select "Path" mode from the toolbar. The editor options dialog will open that additionally prompts for basic path parameters, such as width and extension scheme. When a path is being drawn, it will receive - the settings entered into this dialog. The path properties can even be changed, while the path is - being drawn. Don't forget to click "Apply" to take over the current entries. If the dialog has been - closed unintentionally, it can be reopened with the F3 shortcut. + the settings entered into this dialog. The path properties can be changed, while the path is + being drawn. If the option page has been closed unintentionally, it can be reopened with the F3 shortcut.

To actually draw a path, - choose a layer from the layer panel in which to create a new box. + choose a layer from the layer panel in which to create a new path. Left click at the first vertex, move the mouse to the second vertex, click to place this one and continue to the last vertex. Double-click at the last vertex to finish the path. Press the ESC key to cancel the operation. + Use the backspace key to remove the current segment and go back to the previous segment.

@@ -30,5 +30,21 @@ also during editing.

+

+ + + Paths are often used for wires. KLayout can be configured to provide + vias that allow switching from one layer to another and place a via + between the layers where the paths overlap. Before you can use the via feature, + KLayout has to be equipped with via definitions. Vias are basically PCells which + need to support a specific set of parameters. Via PCells need to expose a number + of descriptors to define the options supported by a particular PCell (e.g. the + layers which are connected or the type of via if multiple flavors are available). + This scheme allows for a lot of flexibility, but needs some coding skills. + In the macro IDE, a code sample is provided which implements two simple vias + for a three-layer metal stack. For more details see , + method "via_types" and . +

+ diff --git a/src/doc/doc/manual/editor_operations.xml b/src/doc/doc/manual/editor_operations.xml index a6d47ad91..6ae56347b 100644 --- a/src/doc/doc/manual/editor_operations.xml +++ b/src/doc/doc/manual/editor_operations.xml @@ -15,6 +15,7 @@ + diff --git a/src/doc/doc/programming/application_api.xml b/src/doc/doc/programming/application_api.xml index 836178481..d4fcf1ebe 100644 --- a/src/doc/doc/programming/application_api.xml +++ b/src/doc/doc/programming/application_api.xml @@ -976,7 +976,7 @@ marker.destroy

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:

    @@ -991,6 +991,10 @@ marker.destroy 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. +
  • Widgets: The plugin factory can provide widgets for the configuration dialog ('File/Setup') and the + editor options dock. Respective callbacks are + and . +

@@ -1048,5 +1052,29 @@ marker.destroy over the mouse in certain circumstances and is supposed to put the plugin into a "watching" instead of "dragging" state.

+

+ A plugin may also create markers for visual feedback and highlights. This can be done explicitly + using marker objects () or in a application-defined fashion by generating + mouse cursors. The API functions for this purpose are , + and . These + functions provide cursors and highlights that match the visual effects of other plugins and + interface with the mouse tracking feature of the application. +

+ +

+ 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 + and . While the first + method provides grid and angle snapping, the second also implements snapping to layout objects. +

+ +

+ 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. +

+ diff --git a/src/drc/drc/built-in-macros/_drc_engine.rb b/src/drc/drc/built-in-macros/_drc_engine.rb index 40f770d77..627a64cc2 100644 --- a/src/drc/drc/built-in-macros/_drc_engine.rb +++ b/src/drc/drc/built-in-macros/_drc_engine.rb @@ -644,6 +644,10 @@ def vstep(x, y = nil) DRCFillStep::new(false, x, y) end + def fill_exclude(excl) + DRCFillExclude::new(excl) + end + def auto_origin DRCFillOrigin::new end diff --git a/src/drc/drc/built-in-macros/_drc_layer.rb b/src/drc/drc/built-in-macros/_drc_layer.rb index 7eee5f905..44466da10 100644 --- a/src/drc/drc/built-in-macros/_drc_layer.rb +++ b/src/drc/drc/built-in-macros/_drc_layer.rb @@ -5755,6 +5755,8 @@ def output(*args) # @li @b multi_origin @/b: lets the algorithm choose the origin and repeats the fill with different origins # until no further fill cell can be fitted. @/li # @li @b fill_pattern(..) @/b: specifies the fill pattern. @/li + # @li @b fill_exclude(excl) @/b: specifies a fill exclude region ('excl' is a polygon layer). This is conceptually + # equivalent to subtracting that layer from the fill region, but usually more efficient. @/li # @/ul # # "fill_pattern" generates a fill pattern object. This object is used for configuring the fill pattern @@ -5895,6 +5897,7 @@ def _fill(with_left, *args) pattern = nil origin = RBA::DPoint::new repeat = false + excl = nil args.each_with_index do |a,ai| if a.is_a?(DRCSource) @@ -5907,6 +5910,11 @@ def _fill(with_left, *args) raise("Duplicate fill pattern specification for '#{m}' at argument ##{ai+1}") end pattern = a + elsif a.is_a?(DRCFillExclude) + if excl + raise("Duplicate exclude region specification for '#{m}' at argument ##{ai+1}") + end + excl = a.excl elsif a.is_a?(DRCFillStep) if a.for_row if row_step @@ -5938,6 +5946,10 @@ def _fill(with_left, *args) column_step = RBA::DVector::new(0, pattern.default_ypitch) end + if !excl + excl = RBA::Region::new + end + dbu_trans = RBA::VCplxTrans::new(1.0 / @engine.dbu) result = nil @@ -5988,6 +6000,7 @@ def _fill(with_left, *args) tp.var("fc_index", fc_index) tp.var("repeat", repeat) tp.var("with_left", with_left) + tp.var("excl", excl) tp.queue(<<"END") var tc_box = _frame.bbox; @@ -5997,8 +6010,8 @@ def _fill(with_left, *args) tile_box = tile_box & tc_box; var left = with_left ? Region.new : nil; repeat ? - (region & tile_box).fill_multi(top_cell, fc_index, fc_box, rs, cs, fill_margin, left, _tile.bbox) : - (region & tile_box).fill(top_cell, fc_index, fc_box, rs, cs, origin, left, fill_margin, left, _tile.bbox); + (region & tile_box).fill_multi(top_cell, fc_index, fc_box, rs, cs, fill_margin, left, _tile.bbox, excl) : + (region & tile_box).fill(top_cell, fc_index, fc_box, rs, cs, origin, left, fill_margin, left, _tile.bbox, excl); with_left && _output(#{result_arg}, left) ) END @@ -6020,9 +6033,9 @@ def _fill(with_left, *args) @engine.run_timed("\"#{m}\" in: #{@engine.src_line}", self.data) do if repeat - self.data.fill_multi(top_cell, fc_index, fc_box, rs, cs, fill_margin, result) + self.data.fill_multi(top_cell, fc_index, fc_box, rs, cs, fill_margin, result, RBA::Box::new, excl) else - self.data.fill(top_cell, fc_index, fc_box, rs, cs, origin, result, fill_margin, result) + self.data.fill(top_cell, fc_index, fc_box, rs, cs, origin, result, fill_margin, result, RBA::Box::new, excl) end end diff --git a/src/drc/drc/built-in-macros/_drc_tags.rb b/src/drc/drc/built-in-macros/_drc_tags.rb index 923ff803e..0999a166a 100644 --- a/src/drc/drc/built-in-macros/_drc_tags.rb +++ b/src/drc/drc/built-in-macros/_drc_tags.rb @@ -475,6 +475,18 @@ def margin(w, h) end + # A wrapper for the fill step definition + class DRCFillExclude + def initialize(excl) + excl.is_a?(DRCLayer) || raise("Exclude layer argument needs to be a DRC layer") + excl.requires_region("Exclude layer") + @excl = excl.data + end + def excl + @excl + end + end + # A wrapper for the fill step definition class DRCFillStep def initialize(for_row, x, y = nil) diff --git a/src/drc/unit_tests/drcSimpleTests.cc b/src/drc/unit_tests/drcSimpleTests.cc index 258c5969d..6ccccb378 100644 --- a/src/drc/unit_tests/drcSimpleTests.cc +++ b/src/drc/unit_tests/drcSimpleTests.cc @@ -1453,6 +1453,16 @@ TEST(47bd_fillWithUsingOutputDeep) run_test (_this, "47b", true); } +TEST(47c_fillWithExcludeArea) +{ + run_test (_this, "47c", false); +} + +TEST(47cd_fillWithExcludeAreaDeep) +{ + run_test (_this, "47c", true); +} + TEST(48_drcWithFragments) { run_test (_this, "48", false); diff --git a/src/edt/edt/AlignOptionsDialog.ui b/src/edt/edt/AlignOptionsDialog.ui index d94795de2..62e60e6a7 100644 --- a/src/edt/edt/AlignOptionsDialog.ui +++ b/src/edt/edt/AlignOptionsDialog.ui @@ -419,8 +419,6 @@ - all_layers_rb - visible_layers_rb h_none_rb h_left_rb h_center_rb @@ -429,7 +427,8 @@ v_top_rb v_center_rb v_bottom_rb - buttonBox + all_layers_rb + visible_layers_rb diff --git a/src/edt/edt/AreaAndPerimeterDialog.ui b/src/edt/edt/AreaAndPerimeterDialog.ui index 9fd1a263a..1e8ffb29d 100644 --- a/src/edt/edt/AreaAndPerimeterDialog.ui +++ b/src/edt/edt/AreaAndPerimeterDialog.ui @@ -108,6 +108,10 @@ The perimeter calculation only takes true outside edges into account. Internal e + + area_le + perimeter_le + diff --git a/src/edt/edt/BoxPropertiesPage.ui b/src/edt/edt/BoxPropertiesPage.ui index 0d0abe629..03f7d3493 100644 --- a/src/edt/edt/BoxPropertiesPage.ui +++ b/src/edt/edt/BoxPropertiesPage.ui @@ -1,7 +1,8 @@ - + + BoxPropertiesPage - - + + 0 0 @@ -9,63 +10,60 @@ 370 - + Form - - - 9 - - + + 6 + + 9 + - - + + QFrame::NoFrame - + QFrame::Raised - - - 0 - - + + 6 + + 0 + - - + + Sans Serif 12 - 75 false true false false - + Box Properties - - - - 7 - 5 + + + 0 0 - + - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -75,13 +73,13 @@ - + Qt::Vertical - + QSizePolicy::Fixed - + 20 10 @@ -90,420 +88,396 @@ - - + + 1 - - + + Corners - - + + 9 - + 6 - - - + + + x = - - - + + + Qt::Horizontal - - - - - 7 - 0 + + + + 1 0 - - - - - 7 - 0 + + + + 1 0 - - - + + + h = - - - - - 7 - 0 + + + + 1 0 - - - + + + Width/height - - - - - 7 - 0 + + + + 1 0 - - - + + + w = - - - + + + Upper right (x/y) - - - + + + false - - - 7 - 0 + + 1 0 - + true - - - + + + Lower left (x/y) - - - + + + y = - - - + + + x = - - - + + + false - - - 7 - 0 + + 1 0 - + true - - - + + + y = - - - + + + Center (x/y) - - - + + + x = - - - + + + y = - - - + + + false - + true - - - + + + false - + true - - + + Center/Size - - + + 9 - + 6 - - - + + + false - - - 7 - 0 + + 1 0 - + true - - - - - 7 - 0 + + + + 1 0 - - - + + + Size (w/h) - - - + + + Qt::Horizontal - - - - - 7 - 0 + + + + 1 0 - - - + + + w = - - - + + + Center (x/y) - - - - - 7 - 0 + + + + 1 0 - - - + + + Lower left (x/y) - - - + + + y = - - - + + + y = - - - - - 7 - 0 + + + + 1 0 - - - + + + h = - - - + + + false - - - 7 - 0 + + 1 0 - + true - - - + + + x = - - - + + + x = - - - + + + false - + true - - - + + + false - + true - - - + + + Upper right (x/y) - - - + + + x = - - - + + + y = @@ -514,13 +488,13 @@ - + Qt::Vertical - + QSizePolicy::Fixed - + 478 10 @@ -529,25 +503,25 @@ - - + + Coordinates in database units - - + + Absolute (accumulated) transformations - + Qt::Vertical - + 478 16 @@ -556,26 +530,26 @@ - - + + QFrame::NoFrame - + QFrame::Raised - - - 0 - - + + 6 + + 0 + - + Qt::Horizontal - + 211 20 @@ -584,15 +558,15 @@ - - + + User Properties - - + + Instantiation diff --git a/src/edt/edt/CopyModeDialog.ui b/src/edt/edt/CopyModeDialog.ui index 563c8580c..188a5ca23 100644 --- a/src/edt/edt/CopyModeDialog.ui +++ b/src/edt/edt/CopyModeDialog.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 164 + 464 + 180 @@ -99,6 +99,11 @@ + + shallow_rb + deep_rb + dont_ask_cbx + diff --git a/src/edt/edt/DistributeOptionsDialog.ui b/src/edt/edt/DistributeOptionsDialog.ui index f70b9c04a..756442336 100644 --- a/src/edt/edt/DistributeOptionsDialog.ui +++ b/src/edt/edt/DistributeOptionsDialog.ui @@ -671,9 +671,22 @@ + h_distribute + h_pitch + h_space + v_distribute + v_pitch + v_space + h_none_rb + h_left_rb + h_center_rb + h_right_rb + v_none_rb + v_top_rb + v_center_rb + v_bottom_rb all_layers_rb visible_layers_rb - buttonBox diff --git a/src/edt/edt/EditorOptionsGeneric.ui b/src/edt/edt/EditorOptionsGeneric.ui index 84c102c71..fb2845d53 100644 --- a/src/edt/edt/EditorOptionsGeneric.ui +++ b/src/edt/edt/EditorOptionsGeneric.ui @@ -470,6 +470,7 @@ grid_cb edit_grid_le snap_objects_cbx + snap_objects_to_grid_cbx conn_angle_cb move_angle_cb hier_sel_cbx diff --git a/src/edt/edt/EditorOptionsPath.ui b/src/edt/edt/EditorOptionsPath.ui index c50521d5a..e42456409 100644 --- a/src/edt/edt/EditorOptionsPath.ui +++ b/src/edt/edt/EditorOptionsPath.ui @@ -268,6 +268,13 @@ + + scrollArea + width_le + type_cb + start_ext_le + end_ext_le + diff --git a/src/edt/edt/EditorOptionsText.ui b/src/edt/edt/EditorOptionsText.ui index 1cc821fd4..5574d8688 100644 --- a/src/edt/edt/EditorOptionsText.ui +++ b/src/edt/edt/EditorOptionsText.ui @@ -42,8 +42,8 @@ 0 0 - 542 - 243 + 528 + 248 @@ -292,6 +292,13 @@ + + scrollArea + text_le + halign_cbx + valign_cbx + size_le + diff --git a/src/edt/edt/InstPropertiesPage.ui b/src/edt/edt/InstPropertiesPage.ui index e233b788f..67461d54d 100644 --- a/src/edt/edt/InstPropertiesPage.ui +++ b/src/edt/edt/InstPropertiesPage.ui @@ -7,7 +7,7 @@ 0 0 666 - 649 + 668 @@ -182,7 +182,7 @@ - 0 + 1 diff --git a/src/edt/edt/MakeCellOptionsDialog.ui b/src/edt/edt/MakeCellOptionsDialog.ui index e40f36a75..e1c533f9f 100644 --- a/src/edt/edt/MakeCellOptionsDialog.ui +++ b/src/edt/edt/MakeCellOptionsDialog.ui @@ -365,6 +365,19 @@ + + cell_name_le + origin_groupbox + lt + ct + rt + lc + cc + rc + lb + cb + rb + diff --git a/src/edt/edt/PointPropertiesPage.ui b/src/edt/edt/PointPropertiesPage.ui index 325d018b9..2cef295b1 100644 --- a/src/edt/edt/PointPropertiesPage.ui +++ b/src/edt/edt/PointPropertiesPage.ui @@ -272,6 +272,8 @@ + x_le + y_le dbu_cb abs_cb prop_pb diff --git a/src/edt/edt/RoundCornerOptionsDialog.ui b/src/edt/edt/RoundCornerOptionsDialog.ui index 1a43d2892..7cfd7c675 100644 --- a/src/edt/edt/RoundCornerOptionsDialog.ui +++ b/src/edt/edt/RoundCornerOptionsDialog.ui @@ -6,7 +6,7 @@ 0 0 - 469 + 472 271 @@ -138,6 +138,12 @@ Leave empty to get the same radius than for outer corners) + + amend_cb + router_le + rinner_le + points_le + diff --git a/src/edt/edt/TextPropertiesPage.ui b/src/edt/edt/TextPropertiesPage.ui index 1ac85ca2d..125981400 100644 --- a/src/edt/edt/TextPropertiesPage.ui +++ b/src/edt/edt/TextPropertiesPage.ui @@ -1,52 +1,51 @@ - + + TextPropertiesPage - - + + 0 0 - 555 - 366 + 568 + 375 - + Form - - + + 9 - + 6 - - - + + + y = - - - - - 7 - 0 + + + + 1 0 - + - + Qt::Vertical - + QSizePolicy::Fixed - + 20 8 @@ -54,19 +53,19 @@ - - - + + + Text size - + - + Qt::Vertical - + 457 51 @@ -74,22 +73,22 @@ - - - + + + Text - + - + Qt::Vertical - + QSizePolicy::Fixed - + 431 8 @@ -97,34 +96,34 @@ - - - + + + x = - - - + + + QFrame::NoFrame - + QFrame::Raised - - - 0 - - + + 6 + + 0 + - + Qt::Horizontal - + 211 20 @@ -133,15 +132,15 @@ - - + + User Properties - - + + Instantiation @@ -149,121 +148,114 @@ - - - + + + Hint: orientation, alignments and size cannot be saved to OASIS files Enable a vector font and text scaling in the setup dialog to show text objects scaled and rotated - - - + + + Orientation - - - + + + Position (x/y) - - - + + + (Leave empty for default) - - - - - 7 - 0 + + + + 0 0 - - - + + + Absolute (accumulated) transformations - - - - - 7 - 0 + + + + 1 0 - - - + + + Coordinates in database units - - - + + + QFrame::NoFrame - + QFrame::Raised - - - 0 - - + + 6 + + 0 + - - + + Sans Serif 12 - 75 false true false false - + Text Properties - - - - 7 - 5 + + + 0 0 - + - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -271,141 +263,149 @@ to show text objects scaled and rotated - - + + - + (r0) - - :/r0_24px.png + + + :/r0_24px.png:/r0_24px.png - + (r90) - - :/r90_24px.png + + + :/r90_24px.png:/r90_24px.png - + (r180) - - :/r180_24px.png + + + :/r180_24px.png:/r180_24px.png - + (r270) - - :/r270_24px.png + + + :/r270_24px.png:/r270_24px.png - + (m0) - - :/m0_24px.png + + + :/m0_24px.png:/m0_24px.png - + (m45) - - :/m45_24px.png + + + :/m45_24px.png:/m45_24px.png - + (m90) - - :/m90_24px.png + + + :/m90_24px.png:/m90_24px.png - + (m135) - - :/m135_24px.png + + + :/m135_24px.png:/m135_24px.png - - + + - - + + - + (Default) - + Left - + Center - + Right - - - + + + Alignment - - - + + + h = - - - + + + v = - - + + - + (Default) - + Top - + Center - + Bottom @@ -427,7 +427,7 @@ to show text objects scaled and rotated inst_pb - + diff --git a/src/edt/edt/edt.pro b/src/edt/edt/edt.pro index 886028431..b46609432 100644 --- a/src/edt/edt/edt.pro +++ b/src/edt/edt/edt.pro @@ -80,7 +80,6 @@ HEADERS += \ edtPartialService.h \ edtPlugin.h \ edtService.h \ - edtUtils.h \ edtCommon.h \ edtDistribute.h \ @@ -91,7 +90,6 @@ SOURCES += \ edtPartialService.cc \ edtPlugin.cc \ edtService.cc \ - edtUtils.cc \ gsiDeclEdt.cc \ edtDistribute.cc \ diff --git a/src/edt/edt/edtBoxService.cc b/src/edt/edt/edtBoxService.cc index 109302a04..8b1cdd173 100644 --- a/src/edt/edt/edtBoxService.cc +++ b/src/edt/edt/edtBoxService.cc @@ -108,10 +108,37 @@ BoxService::do_mouse_move_inactive (const db::DPoint &p) void BoxService::do_mouse_move (const db::DPoint &p) { - do_mouse_move_inactive (p); + lay::PointSnapToObjectResult snap_details = snap2_details (p); + db::DPoint ps = snap_details.snapped_point; + + if (snap_details.object_snap == lay::PointSnapToObjectResult::NoObject) { + + clear_mouse_cursors (); + + db::DPoint px (p.x (), m_p1.y ()); + lay::PointSnapToObjectResult snap_details_x = snap2_details (px); + + db::DPoint py (m_p1.x (), p.y ()); + lay::PointSnapToObjectResult snap_details_y = snap2_details (py); + + if (snap_details_x.object_snap != lay::PointSnapToObjectResult::NoObject) { + ps = db::DPoint (snap_details_x.snapped_point.x (), ps.y ()); + mouse_cursor_from_snap_details (snap_details_x, true /*add*/); + } + + if (snap_details_y.object_snap != lay::PointSnapToObjectResult::NoObject) { + ps = db::DPoint (ps.x (), snap_details_y.snapped_point.y ()); + mouse_cursor_from_snap_details (snap_details_y, true /*add*/); + } + + add_mouse_cursor (ps); + + } else { + mouse_cursor_from_snap_details (snap_details); + } set_cursor (lay::Cursor::cross); - m_p2 = snap2 (p); + m_p2 = ps; update_marker (); } diff --git a/src/edt/edt/edtDialogs.cc b/src/edt/edt/edtDialogs.cc index b31315e86..120a7495f 100644 --- a/src/edt/edt/edtDialogs.cc +++ b/src/edt/edt/edtDialogs.cc @@ -26,7 +26,7 @@ #include "dbLayout.h" #include "edtDialogs.h" -#include "edtUtils.h" +#include "layEditorUtils.h" #include "layObjectInstPath.h" #include "layCellView.h" #include "layLayoutViewBase.h" diff --git a/src/edt/edt/edtEditorOptionsPages.cc b/src/edt/edt/edtEditorOptionsPages.cc index 9db9db7c6..5ed1ac75a 100644 --- a/src/edt/edt/edtEditorOptionsPages.cc +++ b/src/edt/edt/edtEditorOptionsPages.cc @@ -725,7 +725,7 @@ EditorOptionsInstPCellParam::apply (lay::Dispatcher *root) if (pc.first) { const db::PCellDeclaration *pc_decl = layout->pcell_declaration (pc.second); if (pc_decl) { - param = pcell_parameters_to_string (pc_decl->named_parameters (mp_pcell_parameters->get_parameters (&ok))); + param = lay::pcell_parameters_to_string (pc_decl->named_parameters (mp_pcell_parameters->get_parameters (&ok))); } } } diff --git a/src/edt/edt/edtInstService.cc b/src/edt/edt/edtInstService.cc index 765a8513b..3e92d981a 100644 --- a/src/edt/edt/edtInstService.cc +++ b/src/edt/edt/edtInstService.cc @@ -197,7 +197,7 @@ InstService::sync_to_config () dispatcher ()->config_set (cfg_edit_inst_lib_name, m_lib_name); dispatcher ()->config_set (cfg_edit_inst_cell_name, m_cell_or_pcell_name); if (m_is_pcell) { - dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, pcell_parameters_to_string (m_pcell_parameters)); + dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, lay::pcell_parameters_to_string (m_pcell_parameters)); } else { dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, std::string ()); } @@ -531,7 +531,7 @@ InstService::configure (const std::string &name, const std::string &value) if (name == cfg_edit_inst_pcell_parameters) { - std::map pcp = pcell_parameters_from_string (value); + std::map pcp = lay::pcell_parameters_from_string (value); if (pcp != m_pcell_parameters) { m_pcell_parameters = pcp; @@ -772,7 +772,7 @@ InstService::config_finalize () // TODO: it's somewhat questionable to do this inside "config_finalize" as this method is supposed // to reflect changes rather than induce some. if (m_is_pcell) { - dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, pcell_parameters_to_string (m_pcell_parameters)); + dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, lay::pcell_parameters_to_string (m_pcell_parameters)); } else { dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, std::string ()); } diff --git a/src/edt/edt/edtMainService.h b/src/edt/edt/edtMainService.h index c21e94d7c..d8fa67136 100644 --- a/src/edt/edt/edtMainService.h +++ b/src/edt/edt/edtMainService.h @@ -29,10 +29,10 @@ #include "layPlugin.h" #include "layViewObject.h" #include "layMarker.h" +#include "layEditorUtils.h" #include "dbLayout.h" #include "dbShape.h" #include "dbClipboard.h" -#include "edtUtils.h" #include #include diff --git a/src/edt/edt/edtMoveTrackerService.cc b/src/edt/edt/edtMoveTrackerService.cc index 1798ece0d..32a7e2cfa 100644 --- a/src/edt/edt/edtMoveTrackerService.cc +++ b/src/edt/edt/edtMoveTrackerService.cc @@ -61,7 +61,7 @@ MoveTrackerService::issue_edit_events () call_editor_hooks (m_editor_hooks, &edt::EditorHooks::begin_edits); // build the transformation variants cache - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); std::vector services = view ()->get_plugins (); std::vector sel; diff --git a/src/edt/edt/edtPartialService.cc b/src/edt/edt/edtPartialService.cc index fc04e4a08..0bad58cb0 100644 --- a/src/edt/edt/edtPartialService.cc +++ b/src/edt/edt/edtPartialService.cc @@ -1213,7 +1213,7 @@ PartialService::timeout () partial_objects::const_iterator r = transient_selection.begin (); // build the transformation variants cache - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); const lay::CellView &cv = view ()->cellview (r->first.cv_index ()); @@ -1468,7 +1468,7 @@ PartialService::issue_editor_hook_calls (const tl::weak_collection &sel, const db::DTrans &move_trans, std::map &new_edges, std::map &new_points) +PartialService::modify_shape (lay::TransformationVariants &tv, const db::Shape &shape_in, const lay::ObjectInstPath &path, const std::set &sel, const db::DTrans &move_trans, std::map &new_edges, std::map &new_points) { tl_assert (shape_in.shapes () != 0); db::Shape shape = shape_in; @@ -1642,7 +1642,7 @@ void PartialService::transform_selection (const db::DTrans &move_trans) { // build the transformation variants cache - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); // since a shape reference may become invalid while moving it and // because it creates ambiguities, we treat each shape separately: @@ -1798,7 +1798,7 @@ PartialService::mouse_move_event (const db::DPoint &p, unsigned int buttons, boo set_cursor (lay::Cursor::size_all); - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); // drag the vertex or edge/segment if (is_single_point_selection () || is_single_edge_selection ()) { @@ -1862,7 +1862,7 @@ PartialService::mouse_move_event (const db::DPoint &p, unsigned int buttons, boo if (mp_box) { - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); m_p2 = p; mp_box->set_points (m_p1, m_p2); @@ -1922,7 +1922,7 @@ PartialService::mouse_press_event (const db::DPoint &p, unsigned int buttons, bo } else if (! mp_box) { - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); if (m_selection.empty ()) { @@ -2015,7 +2015,7 @@ PartialService::mouse_click_event (const db::DPoint &p, unsigned int buttons, bo if (m_dragging) { - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); if (m_current != m_start) { @@ -2059,7 +2059,7 @@ PartialService::mouse_click_event (const db::DPoint &p, unsigned int buttons, bo view ()->clear_selection (); m_selection = selection; - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); lay::Editable::SelectionMode mode = lay::Editable::Replace; bool shift = ((buttons & lay::ShiftButton) != 0); @@ -2192,7 +2192,7 @@ PartialService::mouse_double_click_event (const db::DPoint &p, unsigned int butt if ((buttons & lay::LeftButton) != 0 && prio) { - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); close_editor_hooks (false); @@ -2215,7 +2215,7 @@ PartialService::mouse_double_click_event (const db::DPoint &p, unsigned int butt db::DPoint new_point_d = snap (p); // build the transformation variants cache - TransformationVariants tv (view (), true /*per cv and layer*/, false /*per cv*/); + lay::TransformationVariants tv (view (), true /*per cv and layer*/, false /*per cv*/); const std::vector *tv_list = tv.per_cv_and_layer (r->first.cv_index (), r->first.layer ()); if (tv_list && ! tv_list->empty ()) { @@ -2304,7 +2304,7 @@ PartialService::mouse_release_event (const db::DPoint &p, unsigned int buttons, if (prio && mp_box) { - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); ui ()->ungrab_mouse (this); @@ -2402,7 +2402,7 @@ PartialService::snap_marker_to_grid (const db::DVector &v, bool &snapped) const db::DVector snapped_to (1.0, 1.0); db::DVector vv = lay::snap_angle (v, move_ac (), &snapped_to); - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); for (auto r = m_selection.begin (); r != m_selection.end (); ++r) { @@ -2548,7 +2548,7 @@ PartialService::selection_bbox () { // build the transformation variants cache // TODO: this is done multiple times - once for each service! - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); const db::DCplxTrans &vp = view ()->viewport ().trans (); lay::TextInfo text_info (view ()); @@ -2938,7 +2938,7 @@ PartialService::single_selected_point () const // build the transformation variants cache and // use only the first one of the explicit transformations // TODO: clarify how this can be implemented in a more generic form or leave it thus. - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); const std::vector *tv_list = tv.per_cv_and_layer (m_selection.begin ()->first.cv_index (), m_selection.begin ()->first.layer ()); const lay::CellView &cv = view ()->cellview (m_selection.begin ()->first.cv_index ()); @@ -2960,7 +2960,7 @@ PartialService::single_selected_edge () const // build the transformation variants cache and // use only the first one of the explicit transformations // TODO: clarify how this can be implemented in a more generic form or leave it thus. - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); const std::vector *tv_list = tv.per_cv_and_layer (m_selection.begin ()->first.cv_index (), m_selection.begin ()->first.layer ()); const lay::CellView &cv = view ()->cellview (m_selection.begin ()->first.cv_index ()); @@ -3042,7 +3042,7 @@ PartialService::do_selection_to_view () if (! m_selection.empty ()) { // build the transformation variants cache - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); for (partial_objects::const_iterator r = m_selection.begin (); r != m_selection.end (); ++r) { @@ -3418,7 +3418,7 @@ PartialService::handle_guiding_shape_changes () // Hint: get_parameters_from_pcell_and_guiding_shapes invalidates the shapes because it resets the changed // guiding shapes. We must not access s->shape after that. - if (! get_parameters_from_pcell_and_guiding_shapes (layout, s->first.cell_index (), parameters_for_pcell)) { + if (! lay::get_parameters_from_pcell_and_guiding_shapes (layout, s->first.cell_index (), parameters_for_pcell)) { return false; } diff --git a/src/edt/edt/edtPartialService.h b/src/edt/edt/edtPartialService.h index 03cdb5bc0..594c73108 100644 --- a/src/edt/edt/edtPartialService.h +++ b/src/edt/edt/edtPartialService.h @@ -30,9 +30,9 @@ #include "layViewObject.h" #include "layRubberBox.h" #include "laySnap.h" +#include "layEditorUtils.h" #include "tlAssert.h" #include "tlDeferredExecution.h" -#include "edtUtils.h" #include "edtConfig.h" #include "edtEditorHooks.h" @@ -400,7 +400,7 @@ public slots: db::DEdge single_selected_edge () const; bool handle_guiding_shape_changes (); void transform_selection (const db::DTrans &move_trans); - db::Shape modify_shape (TransformationVariants &tv, const db::Shape &shape_in, const lay::ObjectInstPath &path, const std::set &sel, const db::DTrans &move_trans, std::map &new_edges, std::map &new_points); + db::Shape modify_shape (lay::TransformationVariants &tv, const db::Shape &shape_in, const lay::ObjectInstPath &path, const std::set &sel, const db::DTrans &move_trans, std::map &new_edges, std::map &new_points); void open_editor_hooks (); void close_editor_hooks (bool commit); diff --git a/src/edt/edt/edtPathService.cc b/src/edt/edt/edtPathService.cc index f5a39eee2..6879dec56 100644 --- a/src/edt/edt/edtPathService.cc +++ b/src/edt/edt/edtPathService.cc @@ -325,7 +325,7 @@ db::Instance PathService::make_via (const db::SelectedViaDefinition &via_def, double w_bottom, double h_bottom, double w_top, double h_top, const db::DPoint &via_pos) { if (! via_def.via_type.cut.is_null ()) { - edt::set_or_request_current_layer (view (), via_def.via_type.cut, cv_index (), false /*don't make current*/); + lay::set_or_request_current_layer (view (), via_def.via_type.cut, cv_index (), false /*don't make current*/); } std::map params; diff --git a/src/edt/edt/edtPlugin.cc b/src/edt/edt/edtPlugin.cc index cc78f30a3..33fbb341f 100644 --- a/src/edt/edt/edtPlugin.cc +++ b/src/edt/edt/edtPlugin.cc @@ -387,7 +387,7 @@ class MainPluginDeclaration menu_entries.push_back (lay::menu_item ("edt::combine_mode", "combine_mode:edit_mode", "@toolbar.end_modes", tl::to_string (tr ("Combine{Select background combination mode}")))); // Key binding only - menu_entries.push_back (lay::menu_item ("edt::via", "via:edit_mode", "@secrets.end", tl::to_string (tr ("Via")) + "(V)")); + menu_entries.push_back (lay::menu_item ("edt::via", "via:edit_mode", "@secrets.end", tl::to_string (tr ("Via")) + "(O)")); menu_entries.push_back (lay::menu_item ("edt::via_up", "via_up:edit_mode", "@secrets.end", tl::to_string (tr ("Via up")))); menu_entries.push_back (lay::menu_item ("edt::via_down", "via_down:edit_mode", "@secrets.end", tl::to_string (tr ("Via down")))); } diff --git a/src/edt/edt/edtRecentConfigurationPage.cc b/src/edt/edt/edtRecentConfigurationPage.cc index e599cf2d4..c2df2e655 100644 --- a/src/edt/edt/edtRecentConfigurationPage.cc +++ b/src/edt/edt/edtRecentConfigurationPage.cc @@ -23,11 +23,11 @@ #if defined(HAVE_QT) #include "edtRecentConfigurationPage.h" -#include "edtUtils.h" #include "layDispatcher.h" #include "layLayoutViewBase.h" #include "layLayerTreeModel.h" #include "layBusy.h" +#include "layEditorUtils.h" #include "dbLibraryManager.h" #include "dbLibrary.h" #include "tlLog.h" @@ -267,7 +267,7 @@ RecentConfigurationPage::render_to (QTreeWidgetItem *item, int column, const std std::map pcp; for (std::list::const_iterator c = m_cfg.begin (); c != m_cfg.end (); ++c, ++pcp_column) { if (c->rendering == RecentConfigurationPage::PCellParameters) { - pcp = pcell_parameters_from_string (values [pcp_column]); + pcp = lay::pcell_parameters_from_string (values [pcp_column]); break; } } @@ -296,7 +296,7 @@ RecentConfigurationPage::render_to (QTreeWidgetItem *item, int column, const std { std::map pcp; try { - pcp = pcell_parameters_from_string (values [column]); + pcp = lay::pcell_parameters_from_string (values [column]); } catch (tl::Exception &ex) { tl::error << tl::to_string (tr ("Configuration error (PCellParameters): ")) << ex.msg (); } @@ -384,7 +384,7 @@ RecentConfigurationPage::item_clicked (QTreeWidgetItem *item) ex.read (cv_index); } - edt::set_or_request_current_layer (view (), lp, cv_index); + lay::set_or_request_current_layer (view (), lp, cv_index); } else { dispatcher ()->config_set (c->cfg_name, v); diff --git a/src/edt/edt/edtService.cc b/src/edt/edt/edtService.cc index 0b8c45f0d..23377fe4f 100644 --- a/src/edt/edt/edtService.cc +++ b/src/edt/edt/edtService.cc @@ -42,27 +42,6 @@ namespace edt { -// ------------------------------------------------------------- -// Convert buttons to an angle constraint - -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; - } - } -} - // ------------------------------------------------------------- Service::Service (db::Manager *manager, lay::LayoutViewBase *view, db::ShapeIterator::flags_type flags) @@ -301,12 +280,10 @@ Service::snap (const db::DPoint &p, const db::DPoint &plast, bool connect) const return snap (ps); } -const int sr_pixels = 8; // TODO: make variable - lay::PointSnapToObjectResult Service::snap2_details (const db::DPoint &p) const { - double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (sr_pixels); + double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (lay::snap_range_pixels ()); return lay::obj_snap (m_snap_to_objects ? view () : 0, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, snap_range); } @@ -319,7 +296,7 @@ Service::snap2 (const db::DPoint &p) const db::DPoint Service::snap2 (const db::DPoint &p, const db::DPoint &plast, bool connect) const { - double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (sr_pixels); + double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (lay::snap_range_pixels ()); return lay::obj_snap (m_snap_to_objects ? view () : 0, plast, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, connect ? connect_ac () : move_ac (), snap_range).snapped_point; } @@ -616,7 +593,7 @@ Service::selection_bbox () { // build the transformation variants cache // TODO: this is done multiple times - once for each service! - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); const db::DCplxTrans &vp = view ()->viewport ().trans (); lay::TextInfo text_info (view ()); @@ -710,7 +687,7 @@ Service::transform (const db::DCplxTrans &trans, const std::vectoris_editable () && prio && (buttons & lay::RightButton) != 0 && m_editing) { - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); do_mouse_transform (p, db::DFTrans (db::DFTrans::r90)); m_alt_ac = lay::AC_Global; return true; @@ -1777,7 +1754,7 @@ Service::do_selection_to_view () m_markers.reserve (selection_size ()); // build the transformation variants cache - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); // prepare a default transformation for empty variants std::vector empty_tv; @@ -1955,7 +1932,7 @@ Service::handle_guiding_shape_changes (const lay::ObjectInstPath &obj, bool comm // Hint: get_parameters_from_pcell_and_guiding_shapes invalidates the shapes because it resets the changed // guiding shapes. We must not access s->shape after that. - if (! get_parameters_from_pcell_and_guiding_shapes (layout, obj.cell_index (), parameters_for_pcell)) { + if (! lay::get_parameters_from_pcell_and_guiding_shapes (layout, obj.cell_index (), parameters_for_pcell)) { return std::make_pair (false, lay::ObjectInstPath ()); } diff --git a/src/edt/edt/edtService.h b/src/edt/edt/edtService.h index 56369272a..a791187e5 100644 --- a/src/edt/edt/edtService.h +++ b/src/edt/edt/edtService.h @@ -33,10 +33,10 @@ #include "laySnap.h" #include "layObjectInstPath.h" #include "layTextInfo.h" +#include "layEditorUtils.h" #include "tlColor.h" #include "dbLayout.h" #include "dbShape.h" -#include "edtUtils.h" #include "edtConfig.h" #include "tlAssert.h" #include "tlException.h" @@ -55,19 +55,15 @@ class PluginDeclarationBase; // ------------------------------------------------------------- -extern lay::angle_constraint_type ac_from_buttons (unsigned int buttons); - -// ------------------------------------------------------------- - /** - * @brief Utility function: serialize PCell parameters into a string + * @brief A helper class that identifies clipboard data for edt:: */ -std::string pcell_parameters_to_string (const std::map ¶meters); - -/** - * @brief Utility: deserialize PCell parameters from a string - */ -std::map pcell_parameters_from_string (const std::string &s); +class EDT_PUBLIC ClipboardData + : public db::ClipboardData +{ +public: + ClipboardData () { } +}; // ------------------------------------------------------------- diff --git a/src/edt/edt/edtShapeService.cc b/src/edt/edt/edtShapeService.cc index 28dc9a461..4664f777b 100644 --- a/src/edt/edt/edtShapeService.cc +++ b/src/edt/edt/edtShapeService.cc @@ -167,7 +167,7 @@ ShapeEditService::change_edit_layer (const db::LayerProperties &lp) } m_layer = (unsigned int) layer; - edt::set_or_request_current_layer (view (), lp, m_cv_index); + lay::set_or_request_current_layer (view (), lp, m_cv_index); if (editing ()) { close_editor_hooks (false); diff --git a/src/img/img/imgObject.cc b/src/img/img/imgObject.cc index 6801059f7..d1a63dffb 100644 --- a/src/img/img/imgObject.cc +++ b/src/img/img/imgObject.cc @@ -1358,6 +1358,9 @@ Object::from_string (const char *str, const char *base_dir) color = true; } else if (ex.test ("mono:")) { color = false; + } else { + // unrecognized token + return; } size_t w = 0; @@ -2499,13 +2502,19 @@ Object::mem_stat (db::MemStatistics *stat, db::MemStatistics::purpose_t purpose, const char * Object::class_name () const { - return "img::Object"; + if (m_layer_binding != db::LayerProperties ()) { + // This makes old KLayout versions ignore these images and not crash + return "img::ObjectV2"; + } else { + return "img::Object"; + } } /** * @brief Registration of the img::Object class in the DUserObject space */ static db::DUserObjectDeclaration class_registrar (new db::user_object_factory_impl ("img::Object")); +static db::DUserObjectDeclaration class_registrar_v2 (new db::user_object_factory_impl ("img::ObjectV2")); } // namespace img diff --git a/src/img/img/imgService.cc b/src/img/img/imgService.cc index fc45ffe99..9e27d9449 100644 --- a/src/img/img/imgService.cc +++ b/src/img/img/imgService.cc @@ -1080,6 +1080,7 @@ Service::edit_cancel () { if (m_move_mode != move_none) { m_move_mode = move_none; + m_selected.clear (); selection_to_view (); } } @@ -1118,14 +1119,27 @@ Service::paste () { if (db::Clipboard::instance ().begin () != db::Clipboard::instance ().end ()) { + std::vector new_objects; + for (db::Clipboard::iterator c = db::Clipboard::instance ().begin (); c != db::Clipboard::instance ().end (); ++c) { const db::ClipboardValue *value = dynamic_cast *> (*c); if (value) { img::Object *image = new img::Object (value->get ()); - mp_view->annotation_shapes ().insert (db::DUserObject (image)); + new_objects.push_back (&mp_view->annotation_shapes ().insert (db::DUserObject (image))); } } + // make new objects selected + + if (! new_objects.empty ()) { + + for (auto r = new_objects.begin (); r != new_objects.end (); ++r) { + m_selected.insert (mp_view->annotation_shapes ().iterator_from_pointer (*r)); + } + + selection_to_view (); + + } } } diff --git a/src/lay/lay/gsiDeclLayConfigPage.cc b/src/lay/lay/gsiDeclLayConfigPage.cc new file mode 100644 index 000000000..cb245498a --- /dev/null +++ b/src/lay/lay/gsiDeclLayConfigPage.cc @@ -0,0 +1,114 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 + +*/ + +#if defined(HAVE_QTBINDINGS) + +#include "gsiDeclLayConfigPage.h" + +#include "gsiQtGuiExternals.h" +#include "gsiQtWidgetsExternals.h" // for Qt5 + +namespace gsi +{ + +ConfigPageImpl::ConfigPageImpl (const std::string &title) + : lay::ConfigPage (0), m_title (title) +{ + // .. nothing yet .. +} + +void +ConfigPageImpl::commit_impl (lay::Dispatcher *root) +{ + lay::ConfigPage::commit (root); +} + +void +ConfigPageImpl::commit (lay::Dispatcher *root) +{ + if (f_commit.can_issue ()) { + f_commit.issue (&ConfigPageImpl::commit_impl, root); + } else { + ConfigPageImpl::commit_impl (root); + } +} + +void +ConfigPageImpl::setup_impl (lay::Dispatcher *root) +{ + lay::ConfigPage::setup (root); +} + +void +ConfigPageImpl::setup (lay::Dispatcher *root) +{ + if (f_setup.can_issue ()) { + f_setup.issue (&ConfigPageImpl::setup_impl, root); + } else { + ConfigPageImpl::setup_impl (root); + } +} + +ConfigPageImpl *new_config_page (const std::string &title) +{ + return new ConfigPageImpl (title); +} + +Class decl_ConfigPage (QT_EXTERNAL_BASE (QFrame) "lay", "ConfigPage", + constructor ("new", &new_config_page, gsi::arg ("title"), + "@brief Creates a new ConfigPage object\n" + "@param title The title of the page and also the position in the configuration page tree\n" + "\n" + "The title has the form 'Group|Page' - e.g. 'Application|Macro Development IDE' will place " + "the configuration page in the 'Application' group and into the 'Macro Development IDE' page." + ) + + callback ("apply", &ConfigPageImpl::commit, &ConfigPageImpl::f_commit, gsi::arg ("dispatcher"), + "@brief Reimplement this method to transfer data from the page to the configuration\n" + "In this method, you should transfer all widget data into corresponding configuration updates.\n" + "Use \\Dispatcher#set_config on the dispatcher object ('dispatcher' argument) to set a configuration parameter.\n" + ) + + callback ("setup", &ConfigPageImpl::setup, &ConfigPageImpl::f_setup, gsi::arg ("dispatcher"), + "@brief Reimplement this method to transfer data from the configuration to the page\n" + "In this method, you should transfer all configuration data to the widgets.\n" + "Use \\Dispatcher#get_config on the dispatcher object ('dispatcher' argument) to get a configuration parameter " + "and set the editing widget's state accordingly.\n" + ), + "@brief The plugin framework's configuration page\n" + "\n" + "This object provides a way to establish plugin-specific configuration pages.\n" + "\n" + "The only way of communication between the page and the plugin is through " + "configuration parameters. One advantage of this approach is that the current state is " + "automatically persisted. Configuration parameters can be obtained by the plugin " + "directly from the \\Dispatcher object) or by listening to 'configure' calls.\n" + "\n" + "For the purpose of data transfer, the configuration page has two methods: 'apply' which is supposed to transfer " + "the editor widget's state into configuration parameters. 'setup' does the inverse and transfer " + "configuration parameters into editor widget states. Both methods are called by the system when " + "some transfer is needed.\n" + "\n" + "This class has been introduced in version 0.30.4.\n" +); + +} + +#endif diff --git a/src/lay/lay/gsiDeclLayConfigPage.h b/src/lay/lay/gsiDeclLayConfigPage.h new file mode 100644 index 000000000..4295b2146 --- /dev/null +++ b/src/lay/lay/gsiDeclLayConfigPage.h @@ -0,0 +1,67 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 + +*/ + +#if !defined(_HDR_gsiDeclLayConfigPage) +#define _HDR_gsiDeclLayConfigPage + +#if defined(HAVE_QTBINDINGS) + +#include "gsiDecl.h" +#include "gsiDeclBasic.h" + +#include "layPluginConfigPage.h" +#include "layLayoutViewBase.h" + +namespace gsi +{ + +class ConfigPageImpl + : public lay::ConfigPage, public gsi::ObjectBase +{ +public: + ConfigPageImpl (const std::string &title); + + virtual std::string title () const + { + return m_title; + } + + void commit_impl (lay::Dispatcher *root); + virtual void commit (lay::Dispatcher *root); + void setup_impl (lay::Dispatcher *root); + virtual void setup (lay::Dispatcher *root); + + gsi::Callback f_commit; + gsi::Callback f_setup; + +private: + tl::weak_ptr mp_view; + tl::weak_ptr mp_dispatcher; + std::string m_title; + std::string m_index; +}; + +} + +#endif + +#endif diff --git a/src/lay/lay/gsiDeclLayEditorOptionsPage.cc b/src/lay/lay/gsiDeclLayEditorOptionsPage.cc new file mode 100644 index 000000000..7a63b0df5 --- /dev/null +++ b/src/lay/lay/gsiDeclLayEditorOptionsPage.cc @@ -0,0 +1,198 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 + +*/ + +#if defined(HAVE_QTBINDINGS) + +#include "gsiDeclLayEditorOptionsPage.h" + +#include "gsiQtGuiExternals.h" +#include "gsiQtWidgetsExternals.h" // for Qt5 + +namespace gsi +{ + +Class decl_EditorOptionsPageBase (QT_EXTERNAL_BASE (QWidget) "lay", "EditorOptionsPageBase", + method ("view", &lay::EditorOptionsPage::view, + "@brief Gets the view object this page is associated with\n" + ) + + method ("title", &lay::EditorOptionsPage::title, + "@brief Gets the title string of the page\n" + ) + + method ("order", &lay::EditorOptionsPage::order, + "@brief Gets the order index of the page\n" + ) + + method ("is_focus_page?", &lay::EditorOptionsPage::is_focus_page, + "@brief Gets a flag indicating whether the page is a focus page\n" + "See \\focus_page= for a description is this attribute.\n" + ) + + method ("focus_page=", &lay::EditorOptionsPage::set_focus_page, gsi::arg ("flag"), + "@brief Sets a flag indicating whether the page is a focus page\n" + "The focus page is the page that is selected when the tab key is pressed during some plugin action.\n" + ) + + method ("is_modal_page?", &lay::EditorOptionsPage::is_modal_page, + "@brief Gets a flag indicating whether the page is a modal page\n" + "See \\modal_page= for a description is this attribute.\n" + ) + + method ("modal_page=", &lay::EditorOptionsPage::set_modal_page, gsi::arg ("flag"), + "@brief Sets a flag indicating whether the page is a modal page\n" + "A modal page is shown in a modal dialog upon \\show. Non-modal pages are shown in the " + "editor options dock.\n" + ) + + method ("show", &lay::EditorOptionsPage::show, + "@brief Shows the page\n" + "@return A value indicating whether the page was opened non-modal (-1), accepted (1) or rejected (0)\n" + "Provided the page is selected because the plugin is active, this method will " + "open a dialog to show the page if it is modal, or locate the page in the editor options " + "dock and bring it to the front if it is non-modal.\n" + "\n" + "Before the page is shown, \\setup is called. When the page is dismissed (accepted), \\apply is called. " + "You can overload these methods to transfer data to and from the configuration space or to perform other " + "actions, not related to configuration parameters." + ) + + method ("apply", &lay::EditorOptionsPage::apply, gsi::arg ("dispatcher"), + "@brief Transfers data from the page to the configuration\n" + ) + + method ("setup", &lay::EditorOptionsPage::setup, gsi::arg ("dispatcher"), + "@brief Transfers data from the configuration to the page\n" + ), + "@brief The plugin framework's editor options page base class\n" + "\n" + "This class is provided as an interface to the base class implementation for various functions.\n" + "You can use these methods in order to pass down events to the original implementation or access\n" + "objects not created in script space.\n" + "\n" + "It features some useful methods such as 'view' and provides a slot to call for triggering a data " + "transfer ('edited').\n" + "\n" + "Note that even though the page class is derived from QWidget, you can call QWidget methods " + "but not overload virtual methods from QWidget.\n" + "\n" + "This class has been introduced in version 0.30.4.\n" +); + +EditorOptionsPageImpl::EditorOptionsPageImpl (const std::string &title, int index) + : lay::EditorOptionsPage (), m_title (title), m_index (index) +{ + // .. nothing yet .. +} + +void +EditorOptionsPageImpl::call_edited () +{ + lay::EditorOptionsPage::edited (); +} + +static void apply_fb (EditorOptionsPageImpl *ep, lay::Dispatcher *root) +{ + ep->lay::EditorOptionsPage::apply (root); +} + +static void setup_fb (EditorOptionsPageImpl *ep, lay::Dispatcher *root) +{ + ep->lay::EditorOptionsPage::setup (root); +} + +void +EditorOptionsPageImpl::apply_impl (lay::Dispatcher *root) +{ + lay::EditorOptionsPage::apply (root); +} + +void +EditorOptionsPageImpl::apply (lay::Dispatcher *root) +{ + if (f_apply.can_issue ()) { + f_apply.issue (&EditorOptionsPageImpl::apply_impl, root); + } else { + EditorOptionsPageImpl::apply_impl (root); + } +} + +void +EditorOptionsPageImpl::setup_impl (lay::Dispatcher *root) +{ + lay::EditorOptionsPage::setup (root); +} + +void +EditorOptionsPageImpl::setup (lay::Dispatcher *root) +{ + if (f_setup.can_issue ()) { + f_setup.issue (&EditorOptionsPageImpl::setup_impl, root); + } else { + EditorOptionsPageImpl::setup_impl (root); + } +} + +EditorOptionsPageImpl *new_editor_options_page (const std::string &title, int index) +{ + return new EditorOptionsPageImpl (title, index); +} + +Class decl_EditorOptionsPage (decl_EditorOptionsPageBase, "lay", "EditorOptionsPage", + constructor ("new", &new_editor_options_page, gsi::arg ("title"), gsi::arg ("index"), + "@brief Creates a new EditorOptionsPage object\n" + "@param title The title of the page\n" + "@param index The position of the page in the tab bar\n" + ) + + method ("edited", &EditorOptionsPageImpl::call_edited, + "@brief Call this method when some entry widget has changed\n" + "When some entry widget (for example 'editingFinished' slot of a QLineEdit), " + "call this method to initiate a transfer of information from the page to the plugin.\n" + ) + + // prevents infinite recursion + method_ext ("apply", &apply_fb, gsi::arg ("dispatcher"), "@hide") + + callback ("apply", &EditorOptionsPageImpl::apply, &EditorOptionsPageImpl::f_apply, gsi::arg ("dispatcher"), + "@brief Reimplement this method to transfer data from the page to the configuration\n" + "In this method, you should transfer all widget data into corresponding configuration updates.\n" + "Use \\Dispatcher#set_config on the dispatcher object ('dispatcher' argument) to set a configuration parameter.\n" + ) + + // prevents infinite recursion + method_ext ("setup", &setup_fb, gsi::arg ("dispatcher"), "@hide") + + callback ("setup", &EditorOptionsPageImpl::setup, &EditorOptionsPageImpl::f_setup, gsi::arg ("dispatcher"), + "@brief Reimplement this method to transfer data from the configuration to the page\n" + "In this method, you should transfer all configuration data to the widgets.\n" + "Use \\Dispatcher#get_config on the dispatcher object ('dispatcher' argument) to get a configuration parameter " + "and set the editing widget's state accordingly.\n" + ), + "@brief The plugin framework's editor options page\n" + "\n" + "This object provides a way to establish plugin-specific editor options pages.\n" + "\n" + "The preferred way of communication between the page and the plugin is through " + "configuration parameters. One advantage of this approach is that the current state is " + "automatically persisted.\n" + "\n" + "For this purpose, the editor options page has two methods: 'apply' which is supposed to transfer " + "the editor widget's state into configuration parameters. 'setup' does the inverse and transfer " + "configuration parameters into editor widget states. Both methods are called by the system when " + "some transfer is needed.\n" + "\n" + "When you want to respond to widget signals and transfer information, call \\edited " + "in the signal slot. This will trigger a transfer (aka 'apply').\n" + "\n" + "This class has been introduced in version 0.30.4.\n" +); + +} + +#endif diff --git a/src/lay/lay/gsiDeclLayEditorOptionsPage.h b/src/lay/lay/gsiDeclLayEditorOptionsPage.h new file mode 100644 index 000000000..0e1598898 --- /dev/null +++ b/src/lay/lay/gsiDeclLayEditorOptionsPage.h @@ -0,0 +1,74 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 + +*/ + +#if !defined(_HDR_gsiDeclLayEditorOptionsPage) +#define _HDR_gsiDeclLayEditorOptionsPage + +#if defined(HAVE_QTBINDINGS) + +#include "gsiDecl.h" +#include "gsiDeclBasic.h" + +#include "layEditorOptionsPage.h" +#include "layLayoutViewBase.h" + +namespace gsi +{ + +class EditorOptionsPageImpl + : public lay::EditorOptionsPage, public gsi::ObjectBase +{ +public: + EditorOptionsPageImpl (const std::string &title, int index); + + virtual std::string title () const + { + return m_title; + } + + virtual int order () const + { + return m_index; + } + + void call_edited (); + virtual void apply (lay::Dispatcher *root); + virtual void setup (lay::Dispatcher *root); + + gsi::Callback f_apply; + gsi::Callback f_setup; + +private: + tl::weak_ptr mp_view; + tl::weak_ptr mp_dispatcher; + std::string m_title; + int m_index; + + void apply_impl (lay::Dispatcher *root); + void setup_impl (lay::Dispatcher *root); +}; + +} + +#endif + +#endif diff --git a/src/lay/lay/gsiDeclLayPlugin.cc b/src/lay/lay/gsiDeclLayPlugin.cc new file mode 100644 index 000000000..418a85bc9 --- /dev/null +++ b/src/lay/lay/gsiDeclLayPlugin.cc @@ -0,0 +1,1168 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "gsiDeclLayPlugin.h" + +#include "gsiDecl.h" +#include "gsiDeclBasic.h" +#include "gsiEnums.h" +#include "layEditorOptionsPages.h" +#include "layCursor.h" +#include "layEditorUtils.h" +#include "edtConfig.h" + +namespace gsi +{ + +static bool has_tracking_position_impl (const lay::EditorServiceBase *p) +{ + return p->lay::EditorServiceBase::has_tracking_position (); +} + +static db::DPoint tracking_position_impl (const lay::EditorServiceBase *p) +{ + return p->lay::EditorServiceBase::tracking_position (); +} + +static void menu_activated_impl (lay::EditorServiceBase *p, const std::string &symbol) +{ + return p->lay::EditorServiceBase::menu_activated (symbol); +} + +static bool configure_impl (lay::EditorServiceBase *p, const std::string &name, const std::string &value) +{ + return p->lay::EditorServiceBase::configure (name, value); +} + +static void config_finalize_impl (lay::EditorServiceBase *p) +{ + p->lay::EditorServiceBase::config_finalize (); +} + +static void deactivated_impl (lay::EditorServiceBase *p) +{ + p->lay::EditorServiceBase::deactivated (); +} + +static void activated_impl (lay::EditorServiceBase *p) +{ + p->lay::EditorServiceBase::activated (); +} + +static bool key_event_impl (lay::EditorServiceBase *p, unsigned int key, unsigned int buttons) +{ + return p->lay::EditorServiceBase::key_event (key, buttons); +} + +static bool mouse_press_event_impl (lay::EditorServiceBase *p, const db::DPoint &pt, unsigned int buttons, bool prio) +{ + return p->lay::EditorServiceBase::mouse_press_event (pt, buttons, prio); +} + +static bool mouse_click_event_impl (lay::EditorServiceBase *p, const db::DPoint &pt, unsigned int buttons, bool prio) +{ + return p->lay::EditorServiceBase::mouse_click_event (pt, buttons, prio); +} + +static bool mouse_double_click_event_impl (lay::EditorServiceBase *p, const db::DPoint &pt, unsigned int buttons, bool prio) +{ + return p->lay::EditorServiceBase::mouse_double_click_event (pt, buttons, prio); +} + +static bool leave_event_impl (lay::EditorServiceBase *p, bool prio) +{ + return p->lay::EditorServiceBase::leave_event (prio); +} + +static bool enter_event_impl (lay::EditorServiceBase *p, bool prio) +{ + return p->lay::EditorServiceBase::enter_event (prio); +} + +static bool mouse_move_event_impl (lay::EditorServiceBase *p, const db::DPoint &pt, unsigned int buttons, bool prio) +{ + return p->lay::EditorServiceBase::mouse_move_event (pt, buttons, prio); +} + +static bool mouse_release_event_impl (lay::EditorServiceBase *p, const db::DPoint &pt, unsigned int buttons, bool prio) +{ + return p->lay::EditorServiceBase::mouse_release_event (pt, buttons, prio); +} + +static bool wheel_event_impl (lay::EditorServiceBase *p, int delta, bool horizontal, const db::DPoint &pt, unsigned int buttons, bool prio) +{ + return p->lay::EditorServiceBase::wheel_event (delta, horizontal, pt, buttons, prio); +} + +static void update_impl (lay::EditorServiceBase *p) +{ + p->lay::EditorServiceBase::update (); +} + +static void drag_cancel_impl (lay::EditorServiceBase *p) +{ + p->lay::EditorServiceBase::drag_cancel (); +} + +Class decl_PluginBase ("lay", "PluginBase", + gsi::method_ext ("tracking_position", &tracking_position_impl, + "@brief Gets the tracking position (base class implementation)" + ) + + gsi::method_ext ("has_tracking_position", &has_tracking_position_impl, + "@brief Gets a value indicating whether the plugin provides a tracking position (base class implementation)" + ) + + gsi::method_ext ("menu_activated", &menu_activated_impl, gsi::arg ("symbol"), + "@brief Gets called when a custom menu item is selected (base class implementation)" + ) + + gsi::method_ext ("configure", &configure_impl, gsi::arg ("name"), gsi::arg ("value"), + "@brief Sends configuration requests to the plugin (base class implementation)" + ) + + gsi::method_ext ("config_finalize", &config_finalize_impl, + "@brief Sends the post-configuration request to the plugin (base class implementation)" + ) + + gsi::method_ext ("key_event", &key_event_impl, gsi::arg ("key"), gsi::arg ("buttons"), + "@brief Handles the key pressed event (base class implementation)" + ) + + gsi::method_ext ("mouse_button_pressed_event", &mouse_press_event_impl, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse button pressed event (base class implementation)" + ) + + gsi::method_ext ("mouse_click_event", &mouse_click_event_impl, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse button click event after the button has been released (base class implementation)" + ) + + gsi::method_ext ("mouse_double_click_event", &mouse_double_click_event_impl, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse button double-click event (base class implementation)" + ) + + gsi::method_ext ("leave_event", &leave_event_impl, gsi::arg ("prio"), + "@brief Handles the leave event (base class implementation)" + ) + + gsi::method_ext ("enter_event", &enter_event_impl, gsi::arg ("prio"), + "@brief Handles the enter event (base class implementation)" + ) + + gsi::method_ext ("mouse_moved_event", &mouse_move_event_impl, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse move event (base class implementation)" + ) + + gsi::method_ext ("mouse_button_released_event", &mouse_release_event_impl, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse button release event (base class implementation)" + ) + + gsi::method_ext ("wheel_event", &wheel_event_impl, gsi::arg ("delta"), gsi::arg ("horizontal"), gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse wheel event (base class implementation)" + ) + + gsi::method_ext ("activated", &activated_impl, + "@brief Gets called when the plugin is activated (base class implementation)" + ) + + gsi::method_ext ("deactivated", &deactivated_impl, + "@brief Gets called when the plugin is deactivated and another plugin is activated (base class implementation)" + ) + + gsi::method_ext ("drag_cancel", &drag_cancel_impl, + "@brief This method is called when some mouse dragging operation should be cancelled (base class implementation)" + ) + + gsi::method_ext ("update", &update_impl, + "@brief Gets called when the view has changed (base class implementation)" + ), + "@brief The plugin base class\n" + "\n" + "This class is provided as an interface to the base class implementation for various functions.\n" + "You can use these methods in order to pass down events to the original implementation.\n" + "\n" + "This class has been introduced in version 0.30.4.\n" +); + + +// HACK: used to track if we're inside a create_plugin method and can be sure that "init" is called +bool s_in_create_plugin = false; + +PluginImpl::PluginImpl () + : lay::EditorServiceBase (), + mp_view (0), mp_dispatcher (0), + m_connect_ac (lay::AC_Any), m_move_ac (lay::AC_Any), + m_snap_to_objects (true), + m_snap_objects_to_grid (true) +{ + if (! s_in_create_plugin) { + throw tl::Exception (tl::to_string (tr ("A PluginBase object can only be created in the PluginFactory's create_plugin method"))); + } +} + +void +PluginImpl::init (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) +{ + mp_view = view; + mp_dispatcher = dispatcher; + lay::EditorServiceBase::init (view); +} + +void +PluginImpl::grab_mouse () +{ + if (ui ()) { + ui ()->grab_mouse (this, false); + } +} + +void +PluginImpl::ungrab_mouse () +{ + if (ui ()) { + ui ()->ungrab_mouse (this); + } +} + +void +PluginImpl::set_cursor (int c) +{ + if (ui ()) { + lay::ViewService::set_cursor ((enum lay::Cursor::cursor_shape) c); + } +} + +void +PluginImpl::menu_activated (const std::string &symbol) +{ + if (f_menu_activated.can_issue ()) { + f_menu_activated.issue (&lay::EditorServiceBase::menu_activated, symbol); + } else { + lay::EditorServiceBase::menu_activated (symbol); + } +} + +db::DPoint +PluginImpl::snap (db::DPoint p) const +{ + // snap according to the grid + if (m_edit_grid == db::DVector ()) { + p = lay::snap_xy (p, m_global_grid); + } else if (m_edit_grid.x () < 1e-6) { + ; // nothing + } else { + p = lay::snap_xy (p, m_edit_grid); + } + + return p; +} + +db::DVector +PluginImpl::snap_vector (db::DVector v) const +{ + // snap according to the grid + if (m_edit_grid == db::DVector ()) { + v = lay::snap_xy (db::DPoint () + v, m_global_grid) - db::DPoint (); + } else if (m_edit_grid.x () < 1e-6) { + ; // nothing + } else { + v = lay::snap_xy (db::DPoint () + v, m_edit_grid) - db::DPoint (); + } + + return v; +} + +db::DPoint +PluginImpl::snap_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac) const +{ + db::DPoint ps = plast + lay::snap_angle (db::DVector (p - plast), connect ? connect_ac (ac) : move_ac (ac)); + return snap (ps); +} + +db::DVector +PluginImpl::snap_delta (const db::DVector &v, bool connect, lay::angle_constraint_type ac) const +{ + return snap_vector (lay::snap_angle (v, connect ? connect_ac (ac) : move_ac (ac))); +} + +db::DPoint +PluginImpl::snap2 (const db::DPoint &p, bool visualize) +{ + double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (lay::snap_range_pixels ()); + auto details = lay::obj_snap (m_snap_to_objects ? view () : 0, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, snap_range); + if (visualize) { + mouse_cursor_from_snap_details (details); + } + return details.snapped_point; +} + +db::DPoint +PluginImpl::snap2_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac, bool visualize) +{ + double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (lay::snap_range_pixels ()); + auto details = lay::obj_snap (m_snap_to_objects ? view () : 0, plast, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, connect ? connect_ac (ac) : move_ac (ac), snap_range); + if (visualize) { + mouse_cursor_from_snap_details (details); + } + return details.snapped_point; +} + +/** + * @brief Captures some edt space configuration events for convencience + */ +void +PluginImpl::configure_edt (const std::string &name, const std::string &value) +{ + edt::EditGridConverter egc; + edt::ACConverter acc; + + if (name == edt::cfg_edit_global_grid) { + egc.from_string (value, m_global_grid); + } else if (name == edt::cfg_edit_grid) { + egc.from_string (value, m_edit_grid); + } else if (name == edt::cfg_edit_snap_to_objects) { + tl::from_string (value, m_snap_to_objects); + } else if (name == edt::cfg_edit_snap_objects_to_grid) { + tl::from_string (value, m_snap_objects_to_grid); + } else if (name == edt::cfg_edit_move_angle_mode) { + acc.from_string (value, m_move_ac); + } else if (name == edt::cfg_edit_connect_angle_mode) { + acc.from_string (value, m_connect_ac); + } else { + lay::EditorServiceBase::configure (name, value); + } +} + +/** + * @brief The implementation does not allow to bypass the base class configuration call + */ +bool +PluginImpl::configure_impl (const std::string &name, const std::string &value) +{ + return f_configure.can_issue () ? f_configure.issue (&PluginImpl::configure, name, value) : lay::EditorServiceBase::configure (name, value); +} + +// for testing +void +PluginImpl::configure_test (const std::string &name, const std::string &value) +{ + configure_edt (name, value); +} + +bool +PluginImpl::configure (const std::string &name, const std::string &value) +{ + configure_edt (name, value); + return configure_impl (name, value); +} + +/** + * @brief The implementation does not allow to bypass the base class configuration call + */ +void +PluginImpl::config_finalize_impl () +{ + f_config_finalize.can_issue () ? f_config_finalize.issue (&PluginImpl::config_finalize) : lay::EditorServiceBase::config_finalize (); +} + +void +PluginImpl::config_finalize () +{ + lay::EditorServiceBase::config_finalize (); + config_finalize_impl (); +} + +bool +PluginImpl::key_event (unsigned int key, unsigned int buttons) +{ + if (f_key_event.can_issue ()) { + return f_key_event.issue (&lay::ViewService::key_event, key, buttons); + } else { + return lay::EditorServiceBase::key_event (key, buttons); + } +} + +bool +PluginImpl::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio) +{ + if (f_mouse_press_event.can_issue ()) { + return f_mouse_press_event.issue (&PluginImpl::mouse_press_event_noref, p, buttons, prio); + } else { + return lay::EditorServiceBase::mouse_press_event (p, buttons, prio); + } +} + +// NOTE: this version doesn't take a point reference which allows us to store the point in script code without generating a reference +bool +PluginImpl::mouse_press_event_noref (db::DPoint p, unsigned int buttons, bool prio) +{ + return mouse_press_event (p, buttons, prio); +} + +bool +PluginImpl::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio) +{ + if (f_mouse_click_event.can_issue ()) { + return f_mouse_click_event.issue (&PluginImpl::mouse_click_event_noref, p, buttons, prio); + } else { + return lay::EditorServiceBase::mouse_click_event (p, buttons, prio); + } +} + +// NOTE: this version doesn't take a point reference which allows us to store the point in script code without generating a reference +bool +PluginImpl::mouse_click_event_noref (db::DPoint p, unsigned int buttons, bool prio) +{ + return mouse_click_event (p, buttons, prio); +} + +bool +PluginImpl::mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio) +{ + if (f_mouse_double_click_event.can_issue ()) { + return f_mouse_double_click_event.issue (&PluginImpl::mouse_double_click_event_noref, p, buttons, prio); + } else { + return lay::EditorServiceBase::mouse_double_click_event (p, buttons, prio); + } +} + +// NOTE: this version doesn't take a point reference which allows us to store the point in script code without generating a reference +bool +PluginImpl::mouse_double_click_event_noref (db::DPoint p, unsigned int buttons, bool prio) +{ + return mouse_double_click_event (p, buttons, prio); +} + +bool +PluginImpl::leave_event (bool prio) +{ + if (f_leave_event.can_issue ()) { + return f_leave_event.issue (&lay::ViewService::leave_event, prio); + } else { + return lay::EditorServiceBase::leave_event (prio); + } +} + +bool +PluginImpl::enter_event (bool prio) +{ + if (f_enter_event.can_issue ()) { + return f_enter_event.issue (&lay::ViewService::enter_event, prio); + } else { + return lay::EditorServiceBase::enter_event (prio); + } +} + +bool +PluginImpl::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) +{ + if (f_mouse_move_event.can_issue ()) { + return f_mouse_move_event.issue (&PluginImpl::mouse_move_event_noref, p, buttons, prio); + } else { + return lay::EditorServiceBase::mouse_move_event (p, buttons, prio); + } +} + +// NOTE: this version doesn't take a point reference which allows us to store the point in script code without generating a reference +bool +PluginImpl::mouse_move_event_noref (db::DPoint p, unsigned int buttons, bool prio) +{ + return mouse_move_event (p, buttons, prio); +} + +bool +PluginImpl::mouse_release_event (const db::DPoint &p, unsigned int buttons, bool prio) +{ + if (f_mouse_release_event.can_issue ()) { + return f_mouse_release_event.issue (&PluginImpl::mouse_release_event_noref, p, buttons, prio); + } else { + return lay::ViewService::mouse_release_event (p, buttons, prio); + } +} + +// NOTE: this version doesn't take a point reference which allows us to store the point in script code without generating a reference +bool +PluginImpl::mouse_release_event_noref (db::DPoint p, unsigned int buttons, bool prio) +{ + return mouse_release_event (p, buttons, prio); +} + +bool +PluginImpl::wheel_event (int delta, bool horizontal, const db::DPoint &p, unsigned int buttons, bool prio) +{ + if (f_wheel_event.can_issue ()) { + return f_wheel_event.issue (&PluginImpl::wheel_event_noref, delta, horizontal, p, buttons, prio); + } else { + return lay::ViewService::wheel_event (delta, horizontal, p, buttons, prio); + } +} + +// NOTE: this version doesn't take a point reference which allows us to store the point in script code without generating a reference +bool +PluginImpl::wheel_event_noref (int delta, bool horizontal, db::DPoint p, unsigned int buttons, bool prio) +{ + return wheel_event (delta, horizontal, p, buttons, prio); +} + +void +PluginImpl::activated_impl () +{ + if (f_activated.can_issue ()) { + f_activated.issue (&PluginImpl::activated_impl); + } +} + +void +PluginImpl::activated () +{ + lay::EditorServiceBase::activated (); + activated_impl (); +} + +void +PluginImpl::deactivated_impl () +{ + if (f_deactivated.can_issue ()) { + f_deactivated.issue (&PluginImpl::deactivated_impl); + } +} + +void +PluginImpl::deactivated () +{ + lay::EditorServiceBase::deactivated (); + deactivated_impl (); +} + +void +PluginImpl::drag_cancel () +{ + if (f_drag_cancel.can_issue ()) { + f_drag_cancel.issue (&lay::EditorServiceBase::drag_cancel); + } else { + lay::EditorServiceBase::drag_cancel (); + } +} + +void +PluginImpl::update () +{ + if (f_update.can_issue ()) { + f_update.issue (&lay::EditorServiceBase::update); + } else { + lay::EditorServiceBase::update (); + } +} + +void +PluginImpl::add_mouse_cursor_dpoint (const db::DPoint &p, bool emphasize) +{ + lay::EditorServiceBase::add_mouse_cursor (p, emphasize); +} + +void +PluginImpl::add_mouse_cursor_point (const db::Point &p, int cv_index, const db::LayerProperties &lp, bool emphasize) +{ + const lay::CellView &cv = view ()->cellview (cv_index); + if (! cv.is_valid ()) { + return; + } + + int layer = cv->layout ().get_layer_maybe (lp); + if (layer < 0) { + return; + } + + lay::TransformationVariants tv (view ()); + const std::vector *tv_list = tv.per_cv_and_layer (cv_index, (unsigned int) layer); + if (! tv_list || tv_list->empty ()) { + return; + } + + lay::EditorServiceBase::add_mouse_cursor (p, cv_index, cv.context_trans (), *tv_list, emphasize); +} + +void +PluginImpl::add_edge_marker_dedge (const db::DEdge &p, bool emphasize) +{ + lay::EditorServiceBase::add_edge_marker (p, emphasize); +} + +void +PluginImpl::add_edge_marker_edge (const db::Edge &p, int cv_index, const db::LayerProperties &lp, bool emphasize) +{ + const lay::CellView &cv = view ()->cellview (cv_index); + if (! cv.is_valid ()) { + return; + } + + int layer = cv->layout ().get_layer_maybe (lp); + if (layer < 0) { + return; + } + + lay::TransformationVariants tv (view ()); + const std::vector *tv_list = tv.per_cv_and_layer (cv_index, (unsigned int) layer); + if (! tv_list || tv_list->empty ()) { + return; + } + + lay::EditorServiceBase::add_edge_marker (p, cv_index, cv.context_trans (), *tv_list, emphasize); +} + +// for testing +bool +PluginImpl::has_tracking_position_test () const +{ + return has_tracking_position (); +} + +bool +PluginImpl::has_tracking_position () const +{ + if (f_has_tracking_position.can_issue ()) { + return f_has_tracking_position.issue (&lay::EditorServiceBase::has_tracking_position); + } else { + return lay::EditorServiceBase::has_tracking_position (); + } +} + +// for testing +db::DPoint +PluginImpl::tracking_position_test () const +{ + return tracking_position (); +} + +db::DPoint +PluginImpl::tracking_position () const +{ + if (f_tracking_position.can_issue ()) { + return f_tracking_position.issue (&lay::EditorServiceBase::tracking_position); + } else { + return lay::EditorServiceBase::tracking_position (); + } +} + +int PluginImpl::focus_page_open () +{ + if (f_focus_page_open.can_issue ()) { + return f_focus_page_open.issue (&lay::EditorServiceBase::focus_page_open); + } else { + return lay::EditorServiceBase::focus_page_open (); + } +} + +lay::angle_constraint_type +PluginImpl::connect_ac (lay::angle_constraint_type ac) const +{ + // m_alt_ac (which is set from mouse buttons) can override the specified connect angle constraint + return ac != lay::AC_Global ? ac : m_connect_ac; +} + +lay::angle_constraint_type +PluginImpl::move_ac (lay::angle_constraint_type ac) const +{ + // m_alt_ac (which is set from mouse buttons) can override the specified move angle constraint + return ac != lay::AC_Global ? ac : m_move_ac; +} + +Class decl_Plugin (decl_PluginBase, "lay", "Plugin", + callback ("menu_activated", &gsi::PluginImpl::menu_activated, &gsi::PluginImpl::f_menu_activated, gsi::arg ("symbol"), + "@brief Gets called when a custom menu item is selected\n" + "When a menu item is clicked which was registered with the plugin factory, the plugin's 'menu_activated' method is " + "called for the current view. The symbol registered for the menu item is passed in the 'symbol' argument." + ) + + method ("configure_test", &gsi::PluginImpl::configure_test, gsi::arg ("name"), gsi::arg ("value"), "@hide") + + callback ("configure", &gsi::PluginImpl::configure_impl, &gsi::PluginImpl::f_configure, gsi::arg ("name"), gsi::arg ("value"), + "@brief Sends configuration requests to the plugin\n" + "@param name The name of the configuration variable as registered in the plugin factory\n" + "@param value The value of the configuration variable\n" + "When a configuration variable is changed, the new value is reported to the plugin by calling the 'configure' method." + ) + + callback ("config_finalize", &gsi::PluginImpl::config_finalize_impl, &gsi::PluginImpl::f_config_finalize, + "@brief Sends the post-configuration request to the plugin\n" + "After all configuration parameters have been sent, 'config_finalize' is called to given the plugin a chance to " + "update its internal state according to the new configuration.\n" + ) + + callback ("key_event", &gsi::PluginImpl::key_event, &gsi::PluginImpl::f_key_event, gsi::arg ("key"), gsi::arg ("buttons"), + "@brief Handles the key pressed event\n" + "This method will called by the view on the active plugin when a button is pressed on the mouse.\n" + "\n" + "If the plugin handles the event, it should return true to indicate that the event should not be processed further." + "\n" + "@param key The Qt key code of the key that was pressed\n" + "@param buttons A combination of the constants in the \\ButtonState class which codes both the mouse buttons and the key modifiers (.e. ShiftButton etc).\n" + "@return True to terminate dispatcher\n" + ) + + callback ("mouse_button_pressed_event", &gsi::PluginImpl::mouse_press_event_noref, &gsi::PluginImpl::f_mouse_press_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse button pressed event\n" + "This method will called by the view when a button is pressed on the mouse.\n" + "\n" + "First, the plugins that grabbed the mouse with \\grab_mouse will receive this event with 'prio' set to true " + "in the reverse order the plugins grabbed the mouse. The loop will terminate if one of the mouse event handlers " + "returns true.\n" + "\n" + "If that is not the case or no plugin has grabbed the mouse, the active plugin receives the mouse event with 'prio' set to true.\n" + "\n" + "If no receiver accepted the mouse event by returning true, it is sent again to all plugins with 'prio' set to false.\n" + "Again, the loop terminates if one of the receivers returns true. The second pass gives inactive plugins a chance to monitor the mouse " + "and implement specific actions - i.e. displaying the current position.\n" + "\n" + "This event is not sent immediately when the mouse button is pressed but when a signification movement for the mouse cursor away from the " + "original position is detected. If the mouse button is released before that, a mouse_clicked_event is sent rather than a press-move-release " + "sequence." + "\n" + "@param p The point at which the button was pressed\n" + "@param buttons A combination of the constants in the \\ButtonState class which codes both the mouse buttons and the key modifiers (.e. LeftButton, ShiftButton etc).\n" + "@return True to terminate dispatcher\n" + ) + + callback ("mouse_click_event", &gsi::PluginImpl::mouse_click_event_noref, &gsi::PluginImpl::f_mouse_click_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse button click event (after the button has been released)\n" + "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse button has been released without moving it.\n" + ) + + callback ("mouse_double_click_event", &gsi::PluginImpl::mouse_double_click_event_noref, &gsi::PluginImpl::f_mouse_double_click_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse button double-click event\n" + "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse button has been double-clicked.\n" + ) + + callback ("leave_event", &gsi::PluginImpl::leave_event, &gsi::PluginImpl::f_leave_event, gsi::arg ("prio"), + "@brief Handles the leave event (mouse leaves canvas area of view)\n" + "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse leaves the canvas area.\n" + "This method does not have a position nor button flags.\n" + ) + + callback ("enter_event", &gsi::PluginImpl::enter_event, &gsi::PluginImpl::f_enter_event, gsi::arg ("prio"), + "@brief Handles the enter event (mouse enters canvas area of view)\n" + "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse enters the canvas area.\n" + "This method does not have a position nor button flags.\n" + ) + + callback ("mouse_moved_event", &gsi::PluginImpl::mouse_move_event_noref, &gsi::PluginImpl::f_mouse_move_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse move event\n" + "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse is moved in the canvas area.\n" + "\n" + "The mouse move event is important for a number of background jobs, such as coordinate display in the status bar.\n" + "Hence, you should not consume the event - i.e. you should return 'false' from this method.\n" + ) + + callback ("mouse_button_released_event", &gsi::PluginImpl::mouse_release_event_noref, &gsi::PluginImpl::f_mouse_release_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse button release event\n" + "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse button is released.\n" + ) + + callback ("wheel_event", &gsi::PluginImpl::wheel_event_noref, &gsi::PluginImpl::f_wheel_event, gsi::arg ("delta"), gsi::arg ("horizontal"), gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse wheel event\n" + "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse wheel is rotated.\n" + "Additional parameters for this event are 'delta' (the rotation angle in units of 1/8th degree) and 'horizontal' which is true when the horizontal wheel was rotated and " + "false if the vertical wheel was rotated.\n" + ) + + callback ("activated", &gsi::PluginImpl::activated, &gsi::PluginImpl::f_activated, + "@brief Gets called when the plugin is activated (selected in the tool bar)\n" + ) + + callback ("deactivated", &gsi::PluginImpl::deactivated, &gsi::PluginImpl::f_deactivated, + "@brief Gets called when the plugin is deactivated and another plugin is activated\n" + ) + + callback ("drag_cancel", &gsi::PluginImpl::drag_cancel, &gsi::PluginImpl::f_drag_cancel, + "@brief Gets called on various occasions when some mouse drag operation should be canceled\n" + "If the plugin implements some press-and-drag or a click-and-drag operation, this callback should " + "cancel this operation and return to some state waiting for a new mouse event." + ) + + callback ("update", &gsi::PluginImpl::update, &gsi::PluginImpl::f_update, + "@brief Gets called when the view has changed\n" + "This method is called in particular if the view has changed the visible rectangle, i.e. after zooming in or out or panning. " + "This callback can be used to update any internal states that depend on the view's state." + ) + + method ("grab_mouse", &gsi::PluginImpl::grab_mouse, + "@brief Redirects mouse events to this plugin, even if the plugin is not active.\n" + ) + + method ("ungrab_mouse", &gsi::PluginImpl::ungrab_mouse, + "@brief Removes a mouse grab registered with \\grab_mouse.\n" + ) + + method ("set_cursor", &gsi::PluginImpl::set_cursor, gsi::arg ("cursor_type"), + "@brief Sets the cursor in the view area to the given type\n" + "Setting the cursor has an effect only inside event handlers, i.e. \\mouse_button_pressed_event. The cursor is not set permanently. Is is reset " + "in the mouse move handler unless a button is pressed or the cursor is explicitly set again in \\mouse_moved_event.\n" + "\n" + "The cursor type is one of the cursor constants in the \\Cursor class, i.e. 'CursorArrow' for the normal cursor." + ) + + method ("has_tracking_position_test", &gsi::PluginImpl::has_tracking_position_test, "@hide") + + callback ("has_tracking_position", &gsi::PluginImpl::has_tracking_position, &gsi::PluginImpl::f_has_tracking_position, + "@brief Gets a value indicating whether the plugin provides a tracking position\n" + "The tracking position is shown in the lower-left corner of the layout window to indicate the current position.\n" + "If this method returns true for the active service, the application will fetch the position by calling \\tracking_position " + "rather than displaying the original mouse position.\n" + "\n" + "The default implementation enables tracking if a mouse cursor has been set using \\add_mouse_cursor.\n" + "When enabling tracking, make sure a reimplementation of \\mouse_moved_event does not consume the\n" + "event and returns 'false'.\n" + "\n" + "This method has been added in version 0.27.6." + ) + + method ("tracking_position_test", &gsi::PluginImpl::tracking_position_test, "@hide") + + callback ("tracking_position", &gsi::PluginImpl::tracking_position, &gsi::PluginImpl::f_tracking_position, + "@brief Gets the tracking position\n" + "See \\has_tracking_position for details.\n" + "\n" + "The default implementation takes the tracking position from a mouse cursor, if you have created one using " + "\\add_mouse_cursor.\n" + "When enabling tracking, make sure a reimplementation of \\mouse_moved_event does not consume the\n" + "event and returns 'false'.\n" + "\n" + "This method has been added in version 0.27.6." + ) + + method ("clear_mouse_cursors", &gsi::PluginImpl::clear_mouse_cursors, + "@brief Clears all existing mouse cursors\n" + "Use this function to remove exisiting mouse cursors (see \\add_mouse_cursor and \\add_edge_marker).\n" + "This method is automatically called when the plugin becomes deactivated.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("add_mouse_cursor", &gsi::PluginImpl::add_mouse_cursor_dpoint, gsi::arg ("p"), gsi::arg ("emphasize", false), + "@brief Creates a cursor to indicate the mouse position\n" + "This function will create a marker that indicates the (for example snapped) mouse position.\n" + "In addition to this, it will establish the position for the tracking cursor, if mouse\n" + "tracking is enabled in the application. You can override the tracking position by reimplementing\n" + "\\tracking_position and \\has_tracking_position.\n" + "\n" + "To enable tracking, make sure a reimplementation of \\mouse_moved_event does not consume the\n" + "event and returns 'false'.\n" + "\n" + "Multiple cursors can be created. In that case, the tracking position is given by the last cursor.\n" + "\n" + "If 'emphasize' is true, the cursor is displayed in a 'stronger' style - i.e. with a double circle instead of a single one.\n" + "\n" + "Before you use this method, clear existing cursors with \\clear_mouse_cursors.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("add_mouse_cursor", &gsi::PluginImpl::add_mouse_cursor_point, gsi::arg ("p"), gsi::arg ("cv_index"), gsi::arg ("layer"), gsi::arg ("emphasize", false), + "@brief Creates a cursor to indicate the mouse position\n" + "This version of this method creates a mouse cursor based on the integer-unit point and\n" + "a source cellview index plus a layer info.\n" + "The cellview index and layer info is used to derive the transformation rules to apply to the " + "point and to compute the final position.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("add_edge_marker", &gsi::PluginImpl::add_edge_marker_dedge, gsi::arg ("e"), gsi::arg ("emphasize", false), + "@brief Creates a cursor to indicate an edge\n" + "This function will create a marker that indicates an edge - for example the edge that a point is snapping to. " + "\n" + "If 'emphasize' is true, the cursor is displayed in a 'stronger' style.\n" + "\n" + "Before you use this method, clear existing edge markers and cursors with \\clear_mouse_cursors.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("add_edge_marker", &gsi::PluginImpl::add_edge_marker_edge, gsi::arg ("e"), gsi::arg ("cv_index"), gsi::arg ("layer"), gsi::arg ("emphasize", false), + "@brief Creates a cursor to indicate an edge\n" + "This version of this method creates an edge marker based on the integer-unit edge and\n" + "a source cellview index plus a layer info.\n" + "The cellview index and layer info is used to derive the transformation rules to apply to the " + "edge and to compute the final position.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("ac_from_buttons", &lay::ac_from_buttons, gsi::arg ("buttons"), + "@brief Creates an angle constraint from a button combination\n" + "This method provides the angle constraints implied by a specific modifier combination, i.e. " + "'Shift' will render ortho snapping. Use this function to generate angle constraints following " + "the established conventions.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("snap", &gsi::PluginImpl::snap, gsi::arg ("p"), + "@brief Snaps a point to the edit grid\n" + "\n" + "@param p The point to snap\n" + "\n" + "If the edit grid is given, the point's x and y components\n" + "are snapped to the edit grid. Otherwise the global grid is used.\n" + "Edit and global grid are set by configuration options.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("snap", &gsi::PluginImpl::snap_vector, gsi::arg ("v"), + "@brief Snaps a vector to the edit grid\n" + "\n" + "@param v The vector to snap\n" + "\n" + "If the edit grid is given, the vector's x and y components\n" + "are snapped to the edit grid. Otherwise the global grid is used.\n" + "Edit and global grid are set by configuration options.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("snap", &gsi::PluginImpl::snap_from_to, gsi::arg ("p"), gsi::arg ("plast"), gsi::arg ("connect", false), gsi::arg ("ac", lay::AC_Global, "AC_Global"), + "@brief Snaps a point to the edit grid with an angle constraint\n" + "\n" + "@param p The point to snap\n" + "@param plast The last point of the connection/move vector\n" + "@param connect true, if the point is an connection vertex, false if it is a move target point\n" + "@param ac Overrides the connect or move angle constraint unless it is \\Plugin#AC_Global\n" + "\n" + "This method snaps point \"p\" relative to the initial point \"plast\". This method\n" + "tries to snap \"p\" to the edit or global grid (edit grid with higher priority), while\n" + "trying to observe the angle constraint that imposes a constraint on the way \"p\"\n" + "can move relative to \"plast\".\n" + "\n" + "The \"connect\" parameter will decide which angle constraint to use, unless \"ac\" specifies\n" + "an angle constraint already. If \"connect\" is true, the line between \"p\" and \"plast\" is regarded a connection\n" + "between points (e.g. a polygon edge) and the connection angle constraint applies. Otherwise\n" + "the move constraint applies.\n" + "\n" + "The angle constraint determines how \"p\" can move in relation to \"plast\" - for example,\n" + "if the angle constraint is \\Plugin#AC_Ortho, \"p\" can only move away from \"plast\" in horizontal or vertical direction.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("snap", &gsi::PluginImpl::snap_delta, gsi::arg ("v"), gsi::arg ("connect", false), gsi::arg ("ac", lay::AC_Global, "AC_Global"), + "@brief Snaps a move vector to the edit grid with and implies an angle constraint\n" + "\n" + "@param v The vector to snap\n" + "@param connect true, if the vector is an connection vector, false if it is a move vector\n" + "@param ac Overrides the connect or move angle constraint unless it is AC_Global\n" + "\n" + "The \"connect\" parameter will decide which angle constraint to use, unless \"ac\" specifies\n" + "an angle constraint already. If \"connect\" is true, the vector is regarded a connection line\n" + "between points (e.g. a polygon edge) and the connection angle constraint applies. Otherwise\n" + "the move constraint applies.\n" + "\n" + "The angle constraint determines how \"p\" can move in relation to \"plast\" - for example,\n" + "if the angle constraint is \\Plugin#AC_Ortho, \"p\" can only move away from \"plast\" in horizontal or vertical direction.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("snap2", &gsi::PluginImpl::snap2, gsi::arg ("p"), gsi::arg ("visualize", false), + "@brief Snaps a point to the edit grid with advanced snapping (including object snapping)\n" + "\n" + "@param p The point to snap\n" + "@param visualize If true, a cursor shape is added to the scene indicating the snap details\n" + "\n" + "This method behaves like the other \"snap2\" variant, but does not allow to specify an\n" + "angle constraint. Only grid constraints and snapping to objects is supported.\n" + "\n" + "If \"visualize\" is true, the function will generate calls to \\add_mouse_cursor or \\add_edge_marker to " + "provide a visualization of the edges or vertexes that the point is snapping to. \\clear_mouse_cursors will " + "be called before.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("snap2", &gsi::PluginImpl::snap2_from_to, gsi::arg ("p"), gsi::arg ("plast"), gsi::arg ("connect", false), gsi::arg ("ac", lay::AC_Global, "AC_Global"), gsi::arg ("visualize", false), + "@brief Snaps a point to the edit grid with an angle constraint with advanced snapping (including object snapping)\n" + "\n" + "@param p The point to snap\n" + "@param plast The last point of the connection or move start point\n" + "@param connect true, if the point is an connection, false if it is a move target point\n" + "@param ac Overrides the connect or move angle constraint unless it is AC_Global\n" + "@param visualize If true, a cursor shape is added to the scene indicating the snap details\n" + "\n" + "This method will snap the point p, given an initial point \"plast\". This includes an angle constraint.\n" + "If \"connect\" is true, the line between \"plast\" and \"p\" is regarded a connection (e.g. a polygon edge).\n" + "If not, the line is regarded a move vector. If \"ac\" is \\Plugin#AC_Global, the angle constraint is \n" + "taken from the connect or move angle constraint, depending on the value of \"connect\". The angle constraint\n" + "determines how \"p\" can move in relation to \"plast\" - for example, if the angle constraint is \\Plugin#AC_Ortho, \n" + "\"p\" can only move away from \"plast\" in horizontal or vertical direction.\n" + "\n" + "This method considers options like global or editing grid or whether the target point\n" + "will snap to another object. The behavior is given by the respective configuration.\n" + "\n" + "If \"visualize\" is true, the function will generate calls to \\add_mouse_cursor or \\add_edge_marker to " + "provide a visualization of the edges or vertexes that the point is snapping to. \\clear_mouse_cursors will " + "be called before.\n" + "\n" + "This method has been added in version 0.30.4." + ) + +#if defined(HAVE_QTBINDINGS) + gsi::method ("editor_options_pages", &gsi::PluginImpl::editor_options_pages, + "@brief Gets the editor options pages which are associated with the view\n" + "The editor options pages are created by the plugin factory class and are associated with this plugin.\n" + "This method allows locating them and using them for plugin-specific purposes.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + gsi::method ("focus_page", &gsi::PluginImpl::focus_page, + "@brief Gets the (first) focus page\n" + "Focus pages are editor options pages that have a true value for \\EditorOptionsPage#is_focus_page.\n" + "The pages can be navigated to quickly or can be shown in a modal dialog from the editor function.\n" + "This method returns the first focus page present in the editor options pages stack.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + callback ("focus_page_open", &gsi::PluginImpl::focus_page_open, &gsi::PluginImpl::f_focus_page_open, + "@brief Gets called when the focus page wants to be opened - i.e. if 'Tab' is pressed during editing\n" + "The default implementation calls \\EditorOptionsPage#show on the focus page.\n" + "This method can be overloaded to provide certain actions before " + "or after the page is shown, specifically if the page is a modal one. For example, it can update the page with current " + "dimensions of a shape that is created and after committing the page, adjust the shape accordingly.\n" + "\n" + "This method has been added in version 0.30.4." + ) + +#endif + gsi::method ("view", &gsi::PluginImpl::view, + "@brief Gets the view object the plugin is associated with\n" + "This method returns the view object that the plugin is associated with.\n" + "\n" + "This convenience method has been added in version 0.30.4." + ) + + gsi::method ("dispatcher", &gsi::PluginImpl::dispatcher, + "@brief Gets the dispatcher object the plugin is associated with\n" + "This method returns the dispatcher object that the plugin is associated with.\n" + "The dispatcher object manages the configuration parameters. 'set_config', 'get_config' and 'commit_config' " + "can be used on this object to get or set configuration parameters. " + "Configuration parameters are a way to persist information and the preferred way of communicating with " + "editor option pages and configuration pages.\n" + "\n" + "This convenience method has been added in version 0.30.4." + ), + "@brief The plugin object\n" + "\n" + "This class provides the actual plugin implementation. Each view gets its own instance of the plugin class. The plugin factory \\PluginFactory class " + "must be specialized to provide a factory for new objects of the Plugin class. See the documentation there for details about the plugin mechanism and " + "the basic concepts.\n" + "\n" + "This class has been introduced in version 0.22.\n" +); + +gsi::Enum decl_AngleConstraintType ("lay", "AngleConstraintType", + gsi::enum_const ("AC_Global", lay::AC_Global, + "@brief Specifies to use the global angle constraint.\n" + ) + + gsi::enum_const ("AC_Any", lay::AC_Any, + "@brief Specifies to use any angle and not snap to a specific direction.\n" + ) + + gsi::enum_const ("AC_Diagonal", lay::AC_Diagonal, + "@brief Specifies to use multiples of 45 degree.\n" + ) + + gsi::enum_const ("AC_Ortho", lay::AC_Ortho, + "@brief Specifies to use multiples of 90 degree.\n" + ) + + gsi::enum_const ("AC_Horizontal", lay::AC_Horizontal, + "@brief Specifies to use horizontal direction only.\n" + ) + + gsi::enum_const ("AC_Vertical", lay::AC_Vertical, + "@brief Specifies to use vertical direction only.\n" + ), + "@brief Specifies angle constraints during snapping.\n" + "\n" + "This enum has been introduced in version 0.30.4." +); + +gsi::ClassExt inject_AngleConstraintType_in_parent (decl_AngleConstraintType.defs ()); + +class CursorNamespace { }; + +static int cursor_shape_none () { return int (lay::Cursor::none); } +static int cursor_shape_arrow () { return int (lay::Cursor::arrow); } +static int cursor_shape_up_arrow () { return int (lay::Cursor::up_arrow); } +static int cursor_shape_cross () { return int (lay::Cursor::cross); } +static int cursor_shape_wait () { return int (lay::Cursor::wait); } +static int cursor_shape_i_beam () { return int (lay::Cursor::i_beam); } +static int cursor_shape_size_ver () { return int (lay::Cursor::size_ver); } +static int cursor_shape_size_hor () { return int (lay::Cursor::size_hor); } +static int cursor_shape_size_bdiag () { return int (lay::Cursor::size_bdiag); } +static int cursor_shape_size_fdiag () { return int (lay::Cursor::size_fdiag); } +static int cursor_shape_size_all () { return int (lay::Cursor::size_all); } +static int cursor_shape_blank () { return int (lay::Cursor::blank); } +static int cursor_shape_split_v () { return int (lay::Cursor::split_v); } +static int cursor_shape_split_h () { return int (lay::Cursor::split_h); } +static int cursor_shape_pointing_hand () { return int (lay::Cursor::pointing_hand); } +static int cursor_shape_forbidden () { return int (lay::Cursor::forbidden); } +static int cursor_shape_whats_this () { return int (lay::Cursor::whats_this); } +static int cursor_shape_busy () { return int (lay::Cursor::busy); } +static int cursor_shape_open_hand () { return int (lay::Cursor::open_hand); } +static int cursor_shape_closed_hand () { return int (lay::Cursor::closed_hand); } + +Class decl_Cursor ("lay", "Cursor", + method ("None", &cursor_shape_none, "@brief 'No cursor (default)' constant for \\Plugin#set_cursor (resets cursor to default)") + + method ("Arrow", &cursor_shape_arrow, "@brief 'Arrow cursor' constant") + + method ("UpArrow", &cursor_shape_up_arrow, "@brief 'Upward arrow cursor' constant") + + method ("Cross", &cursor_shape_cross, "@brief 'Cross cursor' constant") + + method ("Wait", &cursor_shape_wait, "@brief 'Waiting cursor' constant") + + method ("IBeam", &cursor_shape_i_beam, "@brief 'I beam (text insert) cursor' constant") + + method ("SizeVer", &cursor_shape_size_ver, "@brief 'Vertical resize cursor' constant") + + method ("SizeHor", &cursor_shape_size_hor, "@brief 'Horizontal resize cursor' constant") + + method ("SizeBDiag", &cursor_shape_size_bdiag, "@brief 'Backward diagonal resize cursor' constant") + + method ("SizeFDiag", &cursor_shape_size_fdiag, "@brief 'Forward diagonal resize cursor' constant") + + method ("SizeAll", &cursor_shape_size_all, "@brief 'Size all directions cursor' constant") + + method ("Blank", &cursor_shape_blank, "@brief 'Blank cursor' constant") + + method ("SplitV", &cursor_shape_split_v, "@brief 'Split vertical cursor' constant") + + method ("SplitH", &cursor_shape_split_h, "@brief 'split_horizontal cursor' constant") + + method ("PointingHand", &cursor_shape_pointing_hand, "@brief 'Pointing hand cursor' constant") + + method ("Forbidden", &cursor_shape_forbidden, "@brief 'Forbidden area cursor' constant") + + method ("WhatsThis", &cursor_shape_whats_this, "@brief 'Question mark cursor' constant") + + method ("Busy", &cursor_shape_busy, "@brief 'Busy state cursor' constant") + + method ("OpenHand", &cursor_shape_open_hand, "@brief 'Open hand cursor' constant") + + method ("ClosedHand", &cursor_shape_closed_hand, "@brief 'Closed hand cursor' constant"), + "@brief The namespace for the cursor constants\n" + "This class defines the constants for the cursor setting (for example for method \\Plugin#set_cursor)." + "\n" + "This class has been introduced in version 0.22.\n" +); + +class ButtonStateNamespace { }; + +static int const_ShiftButton() { return (int) lay::ShiftButton; } +static int const_ControlButton() { return (int) lay::ControlButton; } +static int const_AltButton() { return (int) lay::AltButton; } +static int const_LeftButton() { return (int) lay::LeftButton; } +static int const_MidButton() { return (int) lay::MidButton; } +static int const_RightButton() { return (int) lay::RightButton; } + +Class decl_ButtonState ("lay", "ButtonState", + method ("ShiftKey", &const_ShiftButton, "@brief Indicates that the Shift key is pressed\nThis constant is combined with other constants within \\ButtonState") + + method ("ControlKey", &const_ControlButton, "@brief Indicates that the Control key is pressed\nThis constant is combined with other constants within \\ButtonState") + + method ("AltKey", &const_AltButton, "@brief Indicates that the Alt key is pressed\nThis constant is combined with other constants within \\ButtonState") + + method ("LeftButton", &const_LeftButton, "@brief Indicates that the left mouse button is pressed\nThis constant is combined with other constants within \\ButtonState") + + method ("MidButton", &const_MidButton, "@brief Indicates that the middle mouse button is pressed\nThis constant is combined with other constants within \\ButtonState") + + method ("RightButton", &const_RightButton, "@brief Indicates that the right mouse button is pressed\nThis constant is combined with other constants within \\ButtonState"), + "@brief The namespace for the button state flags in the mouse events of the Plugin class.\n" + "This class defines the constants for the button state. In the event handler, the button state is " + "indicated by a bitwise combination of these constants. See \\Plugin for further details." + "\n" + "This class has been introduced in version 0.22.\n" +); + +class KeyCodesNamespace { }; + +static int const_KeyEscape() { return (int) lay::KeyEscape; } +static int const_KeyTab() { return (int) lay::KeyTab; } +static int const_KeyBacktab() { return (int) lay::KeyBacktab; } +static int const_KeyBackspace() { return (int) lay::KeyBackspace; } +static int const_KeyReturn() { return (int) lay::KeyReturn; } +static int const_KeyEnter() { return (int) lay::KeyEnter; } +static int const_KeyInsert() { return (int) lay::KeyInsert; } +static int const_KeyDelete() { return (int) lay::KeyDelete; } +static int const_KeyHome() { return (int) lay::KeyHome; } +static int const_KeyEnd() { return (int) lay::KeyEnd; } +static int const_KeyDown() { return (int) lay::KeyDown; } +static int const_KeyUp() { return (int) lay::KeyUp; } +static int const_KeyLeft() { return (int) lay::KeyLeft; } +static int const_KeyRight() { return (int) lay::KeyRight; } +static int const_KeyPageUp() { return (int) lay::KeyPageUp; } +static int const_KeyPageDown() { return (int) lay::KeyPageDown; } + +Class decl_KeyCode ("lay", "KeyCode", + method ("Escape", &const_KeyEscape, "@brief Indicates the Escape key") + + method ("Tab", &const_KeyTab, "@brief Indicates the Tab key") + + method ("Backtab", &const_KeyBacktab, "@brief Indicates the Backtab key") + + method ("Backspace", &const_KeyBackspace, "@brief Indicates the Backspace key") + + method ("Return", &const_KeyReturn, "@brief Indicates the Return key") + + method ("Enter", &const_KeyEnter, "@brief Indicates the Enter key") + + method ("Insert", &const_KeyInsert, "@brief Indicates the Insert key") + + method ("Delete", &const_KeyDelete, "@brief Indicates the Delete key") + + method ("Home", &const_KeyHome, "@brief Indicates the Home key") + + method ("End", &const_KeyEnd, "@brief Indicates the End key") + + method ("Down", &const_KeyDown, "@brief Indicates the Down key") + + method ("Up", &const_KeyUp, "@brief Indicates the Up key") + + method ("Left", &const_KeyLeft, "@brief Indicates the Left key") + + method ("Right", &const_KeyRight, "@brief Indicates the Right key") + + method ("PageUp", &const_KeyPageUp, "@brief Indicates the PageUp key") + + method ("PageDown", &const_KeyPageDown, "@brief Indicates the PageDown key"), + "@brief The namespace for the some key codes.\n" + "This namespace defines some key codes understood by built-in \\LayoutView components. " + "When compiling with Qt, these codes are compatible with Qt's key codes.\n" + "The key codes are intended to be used when directly interfacing with \\LayoutView in non-Qt-based environments.\n" + "\n" + "This class has been introduced in version 0.28.\n" +); + +} diff --git a/src/lay/lay/gsiDeclLayPlugin.h b/src/lay/lay/gsiDeclLayPlugin.h new file mode 100644 index 000000000..c1a442633 --- /dev/null +++ b/src/lay/lay/gsiDeclLayPlugin.h @@ -0,0 +1,153 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 + +*/ + +#ifndef _HDR_gsiDeclLayPlugin +#define _HDR_gsiDeclLayPlugin + +#include "gsiDecl.h" +#include "gsiDeclBasic.h" + +#include "layEditorServiceBase.h" +#include "layLayoutViewBase.h" + +namespace gsi +{ + +class PluginImpl + : public lay::EditorServiceBase +{ +public: + PluginImpl (); + + void init (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher); + void grab_mouse (); + void ungrab_mouse (); + void set_cursor (int c); + + virtual void menu_activated (const std::string &symbol); + db::DPoint snap (db::DPoint p) const; + db::DVector snap_vector (db::DVector v) const; + db::DPoint snap_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac) const; + db::DVector snap_delta (const db::DVector &v, bool connect, lay::angle_constraint_type ac) const; + db::DPoint snap2 (const db::DPoint &p, bool visualize); + db::DPoint snap2_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac, bool visualize); + + // Captures some edt space configuration events for convencience + void configure_edt (const std::string &name, const std::string &value); + // NOTE: The implementation does not allow to bypass the base class configuration call + bool configure_impl (const std::string &name, const std::string &value); + // for testing + void configure_test (const std::string &name, const std::string &value); + virtual bool configure (const std::string &name, const std::string &value); + // NOTE: The implementation does not allow to bypass the base class configuration call + virtual void config_finalize_impl (); + virtual void config_finalize (); + virtual bool key_event (unsigned int key, unsigned int buttons); + virtual bool mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio) ; + bool mouse_press_event_noref (db::DPoint p, unsigned int buttons, bool prio); + virtual bool mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio); + bool mouse_click_event_noref (db::DPoint p, unsigned int buttons, bool prio); + virtual bool mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio); + bool mouse_double_click_event_noref (db::DPoint p, unsigned int buttons, bool prio); + virtual bool leave_event (bool prio); + virtual bool enter_event (bool prio); + virtual bool mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio); + bool mouse_move_event_noref (db::DPoint p, unsigned int buttons, bool prio); + virtual bool mouse_release_event (const db::DPoint &p, unsigned int buttons, bool prio); + bool mouse_release_event_noref (db::DPoint p, unsigned int buttons, bool prio); + virtual bool wheel_event (int delta, bool horizontal, const db::DPoint &p, unsigned int buttons, bool prio); + bool wheel_event_noref (int delta, bool horizontal, db::DPoint p, unsigned int buttons, bool prio); + void activated_impl (); + virtual void activated (); + void deactivated_impl (); + virtual void deactivated (); + virtual void drag_cancel (); + virtual void update (); + void add_mouse_cursor_dpoint (const db::DPoint &p, bool emphasize); + void add_mouse_cursor_point (const db::Point &p, int cv_index, const db::LayerProperties &lp, bool emphasize); + void add_edge_marker_dedge (const db::DEdge &p, bool emphasize); + void add_edge_marker_edge (const db::Edge &p, int cv_index, const db::LayerProperties &lp, bool emphasize); + + // for testing + bool has_tracking_position_test () const; + virtual bool has_tracking_position () const; + + // for testing + db::DPoint tracking_position_test () const; + virtual db::DPoint tracking_position () const; + + virtual int focus_page_open (); + + virtual lay::ViewService *view_service_interface () + { + return this; + } + + lay::LayoutViewBase *view () const + { + return const_cast (mp_view.get ()); + } + + lay::Dispatcher *dispatcher () const + { + return const_cast (mp_dispatcher.get ()); + } + + gsi::Callback f_menu_activated; + gsi::Callback f_configure; + gsi::Callback f_config_finalize; + gsi::Callback f_key_event; + gsi::Callback f_mouse_press_event; + gsi::Callback f_mouse_click_event; + gsi::Callback f_mouse_double_click_event; + gsi::Callback f_leave_event; + gsi::Callback f_enter_event; + gsi::Callback f_mouse_move_event; + gsi::Callback f_mouse_release_event; + gsi::Callback f_wheel_event; + gsi::Callback f_activated; + gsi::Callback f_deactivated; + gsi::Callback f_drag_cancel; + gsi::Callback f_update; + gsi::Callback f_has_tracking_position; + gsi::Callback f_tracking_position; + gsi::Callback f_focus_page_open; + +private: + tl::weak_ptr mp_view; + tl::weak_ptr mp_dispatcher; + + // Angle constraints and grids + lay::angle_constraint_type m_connect_ac, m_move_ac; + db::DVector m_edit_grid; + bool m_snap_to_objects; + bool m_snap_objects_to_grid; + db::DVector m_global_grid; + + lay::angle_constraint_type connect_ac (lay::angle_constraint_type ac) const; + lay::angle_constraint_type move_ac (lay::angle_constraint_type ac) const; +}; + +} + +#endif + diff --git a/src/lay/lay/gsiDeclLayPluginFactory.cc b/src/lay/lay/gsiDeclLayPluginFactory.cc new file mode 100644 index 000000000..85f0541bf --- /dev/null +++ b/src/lay/lay/gsiDeclLayPluginFactory.cc @@ -0,0 +1,602 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "gsiDecl.h" +#include "gsiDeclBasic.h" +#include "gsiEnums.h" + +#include "gsiDeclLayEditorOptionsPage.h" +#include "gsiDeclLayConfigPage.h" +#include "gsiDeclLayPlugin.h" + +#include "layEditorOptionsPages.h" + +namespace gsi +{ + +class PluginFactoryBase; + +static std::map s_factories; +extern bool s_in_create_plugin; + +class PluginFactoryBase + : public lay::PluginDeclaration +{ +public: + PluginFactoryBase () + : PluginDeclaration (), + m_implements_mouse_mode (true), mp_registration (0) + { + // .. nothing yet .. + } + + ~PluginFactoryBase () + { + for (auto f = s_factories.begin (); f != s_factories.end (); ++f) { + if (f->second == this) { + s_factories.erase (f); + break; + } + } + + delete mp_registration; + mp_registration = 0; + } + + void register_gsi (int position, const char *name, const char *title) + { + register_gsi2 (position, name, title, 0); + } + + void register_gsi2 (int position, const char *name, const char *title, const char *icon) + { + // makes the object owned by the C++ side + keep (); + + // remove an existing factory with the same name + std::map ::iterator f = s_factories.find (name); + if (f != s_factories.end () && f->second != this) { + // NOTE: this also removes the plugin from the s_factories list + delete f->second; + } + s_factories[name] = this; + + // cancel any previous registration and register (again) + delete mp_registration; + mp_registration = new tl::RegisteredClass (this, position, name, false /*does not own object*/); + + m_mouse_mode_title = name; + if (title) { + m_mouse_mode_title += "\t"; + m_mouse_mode_title += title; + } + if (icon) { + m_mouse_mode_title += "\t<"; + m_mouse_mode_title += icon; + m_mouse_mode_title += ">"; + } + + // (dynamically) register the plugin class. This will also call initialize if the main window is + // present already. + register_plugin (); + } + + virtual bool configure (const std::string &name, const std::string &value) + { + if (f_configure.can_issue ()) { + return f_configure.issue (&lay::PluginDeclaration::configure, name, value); + } else { + return lay::PluginDeclaration::configure (name, value); + } + } + + virtual void config_finalize () + { + if (f_config_finalize.can_issue ()) { + f_config_finalize.issue (&lay::PluginDeclaration::config_finalize); + } else { + lay::PluginDeclaration::config_finalize (); + } + } + + virtual bool menu_activated (const std::string &symbol) const + { + if (f_menu_activated.can_issue ()) { + return f_menu_activated.issue (&lay::PluginDeclaration::menu_activated, symbol); + } else { + return lay::PluginDeclaration::menu_activated (symbol); + } + } + + virtual void initialize (lay::Dispatcher *root) + { + if (f_initialize.can_issue ()) { + f_initialize.issue (&lay::PluginDeclaration::initialize, root); + } else { + lay::PluginDeclaration::initialize (root); + } + } + + virtual void uninitialize (lay::Dispatcher *root) + { + if (f_uninitialize.can_issue ()) { + f_uninitialize.issue (&lay::PluginDeclaration::uninitialize, root); + } else { + lay::PluginDeclaration::uninitialize (root); + } + } + +#if defined(HAVE_QTBINDINGS) + void add_editor_options_page (EditorOptionsPageImpl *page) const + { + page->keep (); + m_editor_options_pages.push_back (page); + } + + void get_editor_options_pages_impl () const + { + // .. nothing here .. + } + + virtual void get_editor_options_pages (std::vector &pages_out, lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) const + { + try { + + m_editor_options_pages.clear (); + + if (f_get_editor_options_pages.can_issue ()) { + f_get_editor_options_pages.issue (&PluginFactoryBase::get_editor_options_pages_impl); + } else { + get_editor_options_pages_impl (); + } + + for (auto i = m_editor_options_pages.begin (); i != m_editor_options_pages.end (); ++i) { + if (*i) { + (*i)->init (view, dispatcher); + (*i)->set_plugin_declaration (this); + pages_out.push_back (*i); + } + } + + m_editor_options_pages.clear (); + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } catch (std::exception &ex) { + tl::error << ex.what (); + } catch (...) { + } + } + + void add_config_page (ConfigPageImpl *page) const + { + page->keep (); + m_config_pages.push_back (page); + } + + void get_config_pages_impl () const + { + // .. nothing here .. + } + + virtual std::vector > config_pages (QWidget *parent) const + { + std::vector > pages_out; + + try { + + m_config_pages.clear (); + + if (f_config_pages.can_issue ()) { + f_config_pages.issue (&PluginFactoryBase::get_config_pages_impl); + } else { + get_config_pages_impl (); + } + + pages_out.clear (); + for (auto i = m_config_pages.begin (); i != m_config_pages.end (); ++i) { + if (*i) { + (*i)->setParent (parent); + pages_out.push_back (std::make_pair ((*i)->title (), *i)); + } + } + + m_config_pages.clear (); + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } catch (std::exception &ex) { + tl::error << ex.what (); + } catch (...) { + } + + return pages_out; + } +#endif + + virtual lay::Plugin *create_plugin (db::Manager *manager, lay::Dispatcher *root, lay::LayoutViewBase *view) const + { + if (f_create_plugin.can_issue ()) { + return create_plugin_gsi (manager, root, view); + } else { + return lay::PluginDeclaration::create_plugin (manager, root, view); + } + } + + virtual gsi::PluginImpl *create_plugin_gsi (db::Manager *manager, lay::Dispatcher *root, lay::LayoutViewBase *view) const + { + s_in_create_plugin = true; + + gsi::PluginImpl *ret = 0; + try { + + ret = f_create_plugin.issue (&PluginFactoryBase::create_plugin_gsi, manager, root, view); + if (ret) { + ret->init (view, root); + } + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } catch (std::exception &ex) { + tl::error << ex.what (); + } catch (...) { + } + + s_in_create_plugin = false; + + return ret; + } + + virtual void get_menu_entries (std::vector &menu_entries) const + { + menu_entries = m_menu_entries; + } + + virtual void get_options (std::vector < std::pair > &options) const + { + options = m_options; + } + + void add_menu_entry1 (const std::string &menu_name, const std::string &insert_pos) + { + m_menu_entries.push_back (lay::separator (menu_name, insert_pos)); + } + + void add_menu_entry2 (const std::string &symbol, const std::string &menu_name, const std::string &insert_pos, const std::string &title) + { + m_menu_entries.push_back (lay::menu_item (symbol, menu_name, insert_pos, title)); + } + + void add_menu_entry_copy (const std::string &symbol, const std::string &menu_name, const std::string &insert_pos, const std::string ©_from) + { + m_menu_entries.push_back (lay::menu_item_copy (symbol, menu_name, insert_pos, copy_from)); + } + + void add_submenu (const std::string &menu_name, const std::string &insert_pos, const std::string &title) + { + m_menu_entries.push_back (lay::submenu (menu_name, insert_pos, title)); + } + + void add_config_menu_item (const std::string &menu_name, const std::string &insert_pos, const std::string &title, const std::string &cname, const std::string &cvalue) + { + m_menu_entries.push_back (lay::config_menu_item (menu_name, insert_pos, title, cname, cvalue)); + } + + void add_menu_entry3 (const std::string &symbol, const std::string &menu_name, const std::string &insert_pos, const std::string &title, bool sub_menu) + { + if (sub_menu) { + m_menu_entries.push_back (lay::submenu (symbol, menu_name, insert_pos, title)); + } else { + m_menu_entries.push_back (lay::menu_item (symbol, menu_name, insert_pos, title)); + } + } + + void add_option (const std::string &name, const std::string &default_value) + { + m_options.push_back (std::make_pair (name, default_value)); + } + + void has_tool_entry (bool f) + { + m_implements_mouse_mode = f; + } + + virtual bool implements_mouse_mode (std::string &title) const + { + title = m_mouse_mode_title; + return m_implements_mouse_mode; + } + + gsi::Callback f_create_plugin; + gsi::Callback f_initialize; + gsi::Callback f_uninitialize; + gsi::Callback f_configure; + gsi::Callback f_config_finalize; + gsi::Callback f_menu_activated; + gsi::Callback f_get_editor_options_pages; + gsi::Callback f_config_pages; + +private: + std::vector > m_options; + std::vector m_menu_entries; + bool m_implements_mouse_mode; + std::string m_mouse_mode_title; + tl::RegisteredClass *mp_registration; +#if defined(HAVE_QTBINDINGS) + mutable std::vector m_config_pages; + mutable std::vector m_editor_options_pages; +#endif +}; + +Class decl_PluginFactory ("lay", "PluginFactory", + method ("register", &PluginFactoryBase::register_gsi, gsi::arg ("position"), gsi::arg ("name"), gsi::arg ("title"), + "@brief Registers the plugin factory\n" + "@param position An integer that determines the order in which the plugins are created. The internal plugins use the values from 1000 to 50000.\n" + "@param name The plugin name. This is an arbitrary string which should be unique. Hence it is recommended to use a unique prefix, i.e. \"myplugin::ThePluginClass\".\n" + "@param title The title string which is supposed to appear in the tool bar and menu related to this plugin.\n" + "\n" + "Registration of the plugin factory makes the object known to the system. Registration requires that the menu items have been set " + "already. Hence it is recommended to put the registration at the end of the initialization method of the factory class.\n" + ) + + method ("register", &PluginFactoryBase::register_gsi2, gsi::arg ("position"), gsi::arg ("name"), gsi::arg ("title"), gsi::arg ("icon"), + "@brief Registers the plugin factory\n" + "@param position An integer that determines the order in which the plugins are created. The internal plugins use the values from 1000 to 50000.\n" + "@param name The plugin name. This is an arbitrary string which should be unique. Hence it is recommended to use a unique prefix, i.e. \"myplugin::ThePluginClass\".\n" + "@param title The title string which is supposed to appear in the tool bar and menu related to this plugin.\n" + "@param icon The path to the icon that appears in the tool bar and menu related to this plugin.\n" + "\n" + "This version also allows registering an icon for the tool bar.\n" + "\n" + "Registration of the plugin factory makes the object known to the system. Registration requires that the menu items have been set " + "already. Hence it is recommended to put the registration at the end of the initialization method of the factory class.\n" + ) + + callback ("configure", &gsi::PluginFactoryBase::configure, &gsi::PluginFactoryBase::f_configure, gsi::arg ("name"), gsi::arg ("value"), + "@brief Gets called for configuration events for the plugin singleton\n" + "This method can be reimplemented to receive configuration events " + "for the plugin singleton. Before a configuration can be received it must be " + "registered by calling \\add_option in the plugin factories' constructor.\n" + "\n" + "The implementation of this method may return true indicating that the configuration request " + "will not be handled by further modules. It's more cooperative to return false which will " + "make the system distribute the configuration request to other receivers as well.\n" + "\n" + "@param name The configuration key\n" + "@param value The value of the configuration variable\n" + "@return True to stop further processing\n" + ) + + callback ("config_finalize", &gsi::PluginFactoryBase::config_finalize, &gsi::PluginFactoryBase::f_config_finalize, + "@brief Gets called after a set of configuration events has been sent\n" + "This method can be reimplemented and is called after a set of configuration events " + "has been sent to the plugin factory singleton with \\configure. It can be used to " + "set up user interfaces properly for example.\n" + ) + + callback ("menu_activated", &gsi::PluginFactoryBase::menu_activated, &gsi::PluginFactoryBase::f_menu_activated, gsi::arg ("symbol"), + "@brief Gets called when a menu item is selected\n" + "\n" + "Usually, menu-triggered functionality is implemented in the per-view instance of the plugin. " + "However, using this method it is possible to implement functionality globally for all plugin " + "instances. The symbol is the string registered with the specific menu item in the \\add_menu_item " + "call.\n" + "\n" + "If this method was handling the menu event, it should return true. This indicates that the event " + "will not be propagated to other plugins hence avoiding duplicate calls.\n" + ) + + callback ("initialized", &gsi::PluginFactoryBase::initialize, &gsi::PluginFactoryBase::f_initialize, gsi::arg ("dispatcher"), + "@brief Gets called when the plugin singleton is initialized, i.e. when the application has been started.\n" + "@param dispatcher The reference to the \\MainWindow object\n" + ) + + callback ("uninitialized", &gsi::PluginFactoryBase::uninitialize, &gsi::PluginFactoryBase::f_uninitialize, gsi::arg ("dispatcher"), + "@brief Gets called when the application shuts down and the plugin is unregistered\n" + "This event can be used to free resources allocated with this factory singleton.\n" + "@param dispatcher The reference to the \\MainWindow object\n" + ) + + factory_callback ("create_plugin", &gsi::PluginFactoryBase::create_plugin_gsi, &gsi::PluginFactoryBase::f_create_plugin, gsi::arg ("manager"), gsi::arg ("dispatcher"), gsi::arg ("view"), + "@brief Creates the plugin\n" + "This is the basic functionality that the factory must provide. This method must create a plugin of the " + "specific type.\n" + "@param manager The database manager object responsible for handling database transactions\n" + "@param dispatcher The reference to the \\MainWindow object\n" + "@param view The \\LayoutView that is plugin is created for\n" + "@return The new \\Plugin implementation object\n" + ) + + method ("add_menu_entry", &gsi::PluginFactoryBase::add_menu_entry1, gsi::arg ("menu_name"), gsi::arg ("insert_pos"), + "@brief Specifies a separator\n" + "Call this method in the factory constructor to build the menu items that this plugin shall create.\n" + "This specific call inserts a separator at the given position (insert_pos). The position uses abstract menu item paths " + "and \"menu_name\" names the component that will be created. See \\AbstractMenu for a description of the path.\n" + ) + + method ("add_menu_entry", &gsi::PluginFactoryBase::add_menu_entry2, gsi::arg ("symbol"), gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), + "@brief Specifies a menu item\n" + "Call this method in the factory constructor to build the menu items that this plugin shall create.\n" + "This specific call inserts a menu item at the specified position (insert_pos). The position uses abstract menu item paths " + "and \"menu_name\" names the component that will be created. See \\AbstractMenu for a description of the path.\n" + "When the menu item is selected \"symbol\" is the string that is sent to the \\menu_activated callback (either the global one for the factory ot the one of the per-view plugin instance).\n" + "\n" + "@param symbol The string to send to the plugin if the menu is triggered\n" + "@param menu_name The name of entry to create at the given position\n" + "@param insert_pos The position where to create the entry\n" + "@param title The title string for the item. The title can contain a keyboard shortcut in round braces after the title text, i.e. \"My Menu Item(F12)\"\n" + ) + + method ("#add_menu_entry", &gsi::PluginFactoryBase::add_menu_entry3, gsi::arg ("symbol"), gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), gsi::arg ("sub_menu"), + "@brief Specifies a menu item or sub-menu\n" + "Similar to the previous form of \"add_menu_entry\", but this version allows also to create sub-menus by setting the " + "last parameter to \"true\".\n" + "\n" + "With version 0.27 it's more convenient to use \\add_submenu." + ) + + method ("add_menu_item_clone", &gsi::PluginFactoryBase::add_menu_entry_copy, gsi::arg ("symbol"), gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("copy_from"), + "@brief Specifies a menu item as a clone of another one\n" + "Using this method, a menu item can be made a clone of another entry (given as path by 'copy_from').\n" + "The new item will share the \\Action object with the original one, so manipulating the action will change both the original entry " + "and the new entry.\n" + "\n" + "This method has been introduced in version 0.27." + ) + + method ("add_submenu", &gsi::PluginFactoryBase::add_submenu, gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), + "@brief Specifies a menu item or sub-menu\n" + "\n" + "This method has been introduced in version 0.27." + ) + + method ("add_config_menu_item", &gsi::PluginFactoryBase::add_config_menu_item, gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), gsi::arg ("cname"), gsi::arg ("cvalue"), + "@brief Adds a configuration menu item\n" + "\n" + "Menu items created this way will send a configuration request with 'cname' as the configuration parameter name " + "and 'cvalue' as the configuration parameter value.\n" + "If 'cvalue' is a string with a single question mark (\"?\"), the item is a check box that reflects the boolean " + "value of the configuration item.\n" + "\n" + "This method has been introduced in version 0.27." + ) + + method ("add_option", &gsi::PluginFactoryBase::add_option, gsi::arg ("name"), gsi::arg ("default_value"), + "@brief Specifies configuration variables.\n" + "Call this method in the factory constructor to add configuration key/value pairs to the configuration repository. " + "Without specifying configuration variables, the status of a plugin cannot be persisted. " + "\n\n" + "Once the configuration variables are known, they can be retrieved on demand using \"get_config\" from " + "\\MainWindow or listening to \\configure callbacks (either in the factory or the plugin instance). Configuration variables can " + "be set using \"set_config\" from \\MainWindow. This scheme also works without registering the configuration options, but " + "doing so has the advantage that it is guaranteed that a variable with this keys exists and has the given default value initially." + ) + +#if defined(HAVE_QTBINDINGS) + method ("add_editor_options_page", &PluginFactoryBase::add_editor_options_page, gsi::arg ("page"), + "@brief Adds the given editor options page\n" + "See \\create_editor_options_pages how to use this function. The method is effective only in " + "the reimplementation context of this function.\n" + "\n" + "This method has been introduced in version 0.30.4." + ) + + callback ("create_editor_options_pages", &PluginFactoryBase::get_editor_options_pages_impl, &PluginFactoryBase::f_get_editor_options_pages, + "@brief Creates the editor option pages\n" + "The editor option pages are widgets of type \\EditorOptionsPage. These Qt widgets " + "are displayed in a seperate dock (the 'editor options') and become visible when the plugin is active - i.e. " + "its mode is selected. Use this method to provide customized pages that will be displayed in the " + "editor options dock.\n" + "\n" + "In order to create config pages, instantiate a \\EditorOptionsPage object and " + "call \\add_editor_options_page to register it.\n" + "\n" + "This method has been introduced in version 0.30.4." + ) + + method ("add_config_page", &PluginFactoryBase::add_config_page, gsi::arg ("page"), + "@brief Adds the given configuration page\n" + "See \\create_config_pages how to use this function. The method is effective only in " + "the reimplementation context of this function.\n" + "\n" + "This method has been introduced in version 0.30.4." + ) + + callback ("create_config_pages", &PluginFactoryBase::get_config_pages_impl, &PluginFactoryBase::f_config_pages, + "@brief Creates the configuration widgets\n" + "The configuration pages are widgets that are displayed in the " + "configuration dialog ('File/Setup'). Every plugin can create multiple such " + "widgets and specify, where these widgets are displayed. The widgets are of type \\ConfigPage.\n" + "\n" + "The title string also specifies the location of the widget in the " + "configuration page hierarchy. See \\ConfigPage for more details.\n" + "\n" + "In order to create config pages, instantiate a \\ConfigPage object and " + "call \\add_config_page to register it.\n" + "\n" + "This method has been introduced in version 0.30.4." + ) + +#endif + method ("has_tool_entry=", &gsi::PluginFactoryBase::has_tool_entry, gsi::arg ("f"), + "@brief Enables or disables the tool bar entry\n" + "Initially this property is set to true. This means that the plugin will have a visible entry in the toolbar. " + "This property can be set to false to disable this feature. In that case, the title and icon given on registration will be ignored. " + ), + "@brief The plugin framework's plugin factory object\n" + "\n" + "Plugins are components that extend KLayout's functionality in various aspects. Scripting support exists " + "currently for providing mouse mode handlers and general on-demand functionality connected with a menu " + "entry.\n" + "\n" + "Plugins are objects that implement the \\Plugin interface. Each layout view is associated with one instance " + "of such an object. The PluginFactory is a singleton which is responsible for creating \\Plugin objects and " + "providing certain configuration information such as where to put the menu items connected to this plugin and " + "what configuration keys are used.\n" + "\n" + "An implementation of PluginFactory must at least provide an implementation of \\create_plugin. This method " + "must instantiate a new object of the specific plugin.\n" + "\n" + "After the factory has been created, it must be registered in the system using one of the \\register methods. " + "It is therefore recommended to put the call to \\register at the end of the \"initialize\" method. For the registration " + "to work properly, the menu items must be defined before \\register is called.\n" + "\n" + "The following features can also be implemented:\n" + "\n" + "@
    \n" + " @
  • Reserve keys in the configuration file using \\add_option in the constructor@
  • \n" + " @
  • Create menu items by using \\add_menu_entry in the constructor@
  • \n" + " @
  • Set the title for the mode entry that appears in the tool bar using the \\register argument@
  • \n" + " @
  • Provide global functionality (independent from the layout view) using \\configure or \\menu_activated@
  • \n" + "@
\n" + "\n" + "This is a simple example for a plugin in Ruby. It switches the mouse cursor to a 'cross' cursor when it is active:\n" + "\n" + "@code\n" + "class PluginTestFactory < RBA::PluginFactory\n" + "\n" + " # Constructor\n" + " def initialize\n" + " # registers the new plugin class at position 100000 (at the end), with name\n" + " # \"my_plugin_test\" and title \"My plugin test\"\n" + " register(100000, \"my_plugin_test\", \"My plugin test\")\n" + " end\n" + " \n" + " # Create a new plugin instance of the custom type\n" + " def create_plugin(manager, dispatcher, view)\n" + " return PluginTest.new\n" + " end\n" + "\n" + "end\n" + "\n" + "# The plugin class\n" + "class PluginTest < RBA::Plugin\n" + " def mouse_moved_event(p, buttons, prio)\n" + " if prio\n" + " # Set the cursor to cross if our plugin is active.\n" + " set_cursor(RBA::Cursor::Cross)\n" + " end\n" + " # Returning false indicates that we don't want to consume the event.\n" + " # This way for example the cursor position tracker still works.\n" + " false\n" + " end\n" + " def mouse_click_event(p, buttons, prio)\n" + " if prio\n" + " puts \"mouse button clicked.\"\n" + " # This indicates we want to consume the event and others don't receive the mouse click\n" + " # with prio = false.\n" + " return true\n" + " end\n" + " # don't consume the event if we are not active.\n" + " false\n" + " end\n" + "end\n" + "\n" + "# Instantiate the new plugin factory.\n" + "PluginTestFactory.new\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.22.\n" +); + +} diff --git a/src/lay/lay/lay.pro b/src/lay/lay/lay.pro index 23295c1f8..9ccbc0867 100644 --- a/src/lay/lay/lay.pro +++ b/src/lay/lay/lay.pro @@ -7,6 +7,9 @@ include($$PWD/../../lib.pri) DEFINES += MAKE_LAY_LIBRARY HEADERS = \ + gsiDeclLayConfigPage.h \ + gsiDeclLayEditorOptionsPage.h \ + gsiDeclLayPlugin.h \ layApplication.h \ layClipDialog.h \ layControlWidgetStack.h \ @@ -117,8 +120,12 @@ FORMS = \ SOURCES = \ gsiDeclLayApplication.cc \ + gsiDeclLayConfigPage.cc \ + gsiDeclLayEditorOptionsPage.cc \ gsiDeclLayHelpDialog.cc \ gsiDeclLayMainWindow.cc \ + gsiDeclLayPlugin.cc \ + gsiDeclLayPluginFactory.cc \ layApplication.cc \ layClipDialog.cc \ layControlWidgetStack.cc \ diff --git a/src/lay/lay/layMacroTemplates.qrc b/src/lay/lay/layMacroTemplates.qrc index 6bb0a8692..7509c8940 100644 --- a/src/lay/lay/layMacroTemplates.qrc +++ b/src/lay/lay/layMacroTemplates.qrc @@ -5,6 +5,8 @@ macro_templates/new_macro.lym macro_templates/new_text_file.txt macro_templates/new_ruby_file.rb + macro_templates/drag_box_sample.lym + macro_templates/drag_box_sample_python.lym macro_templates/pcell.lym macro_templates/pcell_sample.lym macro_templates/qt_designer.lym diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index 7b52b97b9..72430e410 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -1664,32 +1664,38 @@ MainWindow::select_mode (int m) } } - // if the current mode supports editing, show the editor options panel + update_editor_options_dock (); - const lay::PluginDeclaration *pd_sel = 0; - for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { - const lay::PluginDeclaration *pd = cls.operator-> (); - if (pd->id () == m_mode) { - pd_sel = pd; - } - } + } +} - bool eo_visible = false; - if (mp_eo_stack && pd_sel) { - eo_visible = pd_sel->editable_enabled (); - } - if (current_view () && eo_visible) { - lay::EditorOptionsPages *eo_pages = current_view ()->editor_options_pages (); - if (! eo_pages || ! eo_pages->has_content ()) { - eo_visible = false; - } +void +MainWindow::update_editor_options_dock () +{ + // if the current mode supports editing, show the editor options panel + + const lay::PluginDeclaration *pd_sel = 0; + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + const lay::PluginDeclaration *pd = cls.operator-> (); + if (pd->id () == m_mode) { + pd_sel = pd; } + } - if (eo_visible != m_eo_visible) { - m_eo_visible = eo_visible; - show_dock_widget (mp_eo_dock_widget, m_eo_visible); + bool eo_visible = false; + if (mp_eo_stack && pd_sel) { + eo_visible = pd_sel->editable_enabled (); + } + if (current_view () && eo_visible) { + lay::EditorOptionsPages *eo_pages = current_view ()->editor_options_pages (); + if (! eo_pages || ! eo_pages->has_content ()) { + eo_visible = false; } + } + if (eo_visible != m_eo_visible) { + m_eo_visible = eo_visible; + show_dock_widget (mp_eo_dock_widget, m_eo_visible); } } @@ -2439,6 +2445,7 @@ MainWindow::select_view (int index) current_view_changed (); + update_editor_options_dock (); clear_current_pos (); edits_enabled_changed (); clear_message (); @@ -4381,6 +4388,11 @@ MainWindow::plugin_registered (lay::PluginDeclaration *cls) for (std::vector ::iterator vp = mp_views.begin (); vp != mp_views.end (); ++vp) { (*vp)->view ()->create_plugins (); } + + // regenerate the setup form + delete mp_setup_form; + mp_setup_form = new SettingsForm (0, dispatcher (), "setup_form"), + mp_setup_form->setup (); } void diff --git a/src/lay/lay/layMainWindow.h b/src/lay/lay/layMainWindow.h index 1f6fc2f69..dee59857d 100644 --- a/src/lay/lay/layMainWindow.h +++ b/src/lay/lay/layMainWindow.h @@ -95,6 +95,7 @@ class ProgressWidget; class LAY_PUBLIC MainWindow : public QMainWindow, public tl::Object, + public gsi::ObjectBase, public lay::DispatcherDelegate { Q_OBJECT @@ -857,6 +858,7 @@ protected slots: void interactive_close_view (int from, int to, bool invert_range, bool all_cellviews); void call_on_current_view (void (lay::LayoutView::*func) (), const std::string &op_desc); void current_view_changed (); + void update_editor_options_dock (); void update_window_title (); void update_tab_title (int i); void add_view (LayoutViewWidget *view); diff --git a/src/lay/lay/macro_templates/drag_box_sample.lym b/src/lay/lay/macro_templates/drag_box_sample.lym new file mode 100644 index 000000000..454c0d8d3 --- /dev/null +++ b/src/lay/lay/macro_templates/drag_box_sample.lym @@ -0,0 +1,431 @@ + + + A plugin sample\nThis sample provides a box drawing feature and demonstrates UI components and snapping + general + true + false + false + + ruby + # 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. + +module DragBox + +CFG_COLOR = "drag-box-color" +CFG_WIDTH = "drag-box-width" + +# 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 + +class DragBoxEditorOptionsPage < RBA::EditorOptionsPage + + # Creates a new page with title "Options" and at position 1 (second from left) + + def initialize + + 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) + + # connect the spin box value change with the "edited" slot + # which will result in a call of "apply". + @spin_box.valueChanged = lambda { |x| self.edited } + + end + + # "setup" is called when the page needs to be populated with information - + # i.e. on first show. + def setup(dispatcher) + begin + @spin_box.setValue(dispatcher.get_config(CFG_WIDTH).to_i) + rescue + @spin_box.setValue(1) + end + end + + # "apply" is called when the page is requested to submit the entered + # values to the plugin. Usually this should be done via configuration + # events. + def apply(dispatcher) + dispatcher.set_config(CFG_WIDTH, @spin_box.value.to_s) + end + +end + +# 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". + +class DragBoxFocusPage < RBA::EditorOptionsPage + + # Creates a new page with title "Options" and at + # position 1 (second from left) + + attr_accessor :box + attr_accessor :pfix + attr_accessor :update_box + + def initialize + + super("Geometry", 2) + + self.focus_page = true + self.modal_page = true + + @box = RBA::DBox::new + @pfix = RBA::DPoint::new + + layout = RBA::QGridLayout::new(self) + layout.setColumnStretch(1, 1) + + label = RBA::QLabel::new("Width", self) + layout.addWidget(label, 0, 0, 1, 1) + @le_width = RBA::QLineEdit::new(self) + layout.addWidget(@le_width, 0, 1, 1, 1) + + label = RBA::QLabel::new("Height", self) + layout.addWidget(label, 1, 0, 1, 1) + @le_height = RBA::QLineEdit::new(self) + layout.addWidget(@le_height, 1, 1, 1, 1) + + layout.setRowStretch(2, 1) + + end + + # Is called when the page needs to be set up. + # We assume that the client has properly set up self.box + def setup(dispatcher) + @le_width.text = "%.12g" % @box.width + @le_height.text = "%.12g" % @box.height + end + + # 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. + + def apply(dispatcher) + + # fetches the coordinates from the entry boxes + # throws an exception in case of an error + x = @le_width.text.to_f + y = @le_height.text.to_f + + # prepares a new box with the given dimensions + # using the initial point ("pfix") and considering + # the drag direction + t = b = @pfix.y + l = r = @pfix.x + + if @box.bottom < t - 1e-10 + b = t - y + else + t = b + y + end + + if @box.left < l - 1e-10 + l = r - x + else + r = l + x + end + + # issue the event (call the handler) to inform the plugin of this change + if @update_box + @update_box.call(RBA::DBox::new(l, b, r, t)) + end + + end + +end + +# The widget placed into the configuration page + +# 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. + +class DragBoxConfigPage < RBA::ConfigPage + + # Initializes the page. Places it on a new section ("Drag Box") and "Configure" page + # and creates a single entry field. + def initialize + + super("Drag Box|Configure") + + 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 + + # This method is called to request an update of the entry fields + def setup(dispatcher) + @line_edit.setText(dispatcher.get_config(CFG_COLOR)) + end + + # This method is called to request a transfer of the edited values + # to the configuration space. + def apply(dispatcher) + dispatcher.set_config(CFG_COLOR, @line_edit.text) + end +end + +# The custom plugin implementation. + +class DragBoxPlugin < RBA::Plugin + + def initialize(view) + super() + @marker = nil + @last_marker = nil + @box = nil + @start_point = nil + @view = view + @color = nil + @width = 1 + end + + # This method receives configuration callbacks + def configure(name, value) + if name == CFG_COLOR + # configure marker color + begin + @color = value != "" ? value.to_i(16) : nil + rescue + @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 + + # clears all markers + def _clear_marker + [ @marker, @last_marker ].each { |marker| marker && marker._destroy } + @marker = nil + @last_marker = nil + end + + # stops dragging the marker and copy to a static one + def _finish + if @last_marker + @last_marker._destroy + end + @last_marker = @marker + @marker = nil + # reset to idle + self.ungrab_mouse + RBA::MainWindow.instance.message("Box finished: " + @box.to_s, 10000) + end + + # updates the marker with the current box + def _update_marker + if ! @marker + @marker = RBA::Marker::new(@view) + self._configure_marker + end + @marker.set(@box) + end + + # changes the marker's appearance + def _configure_marker + if @marker + @marker.line_style = 2 # short-dashed + @marker.vertex_size = 0 # no vertexes + @marker.line_width = @width + @marker.color = @color ? (@color | 0xff000000) : 0 # auto + end + end + + # Updates the box with the given value and updates the marker. + # This method is bound to the focus page handler when needed. + def _update_box(box) + @box = box + self._update_marker + end + + # overloaded callback: the focus page is requested + def focus_page_open + + # stop unless dragging + if !@marker + return + end + + # 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 = @box + fp.pfix = @start_point + fp.update_box = lambda { |box| self._update_box(box) } + ret = fp.show + fp.update_box = nil + if ret == 1 + # accepted: stop dragging now, we are done. + self._finish + end + + return ret + + end + + # overloaded callback: + # plugin is activated - i.e. the mode is selected + def activated + RBA::MainWindow.instance.message("Click on point to start dragging a box", 10000) + end + + # overloaded callback: + # plugin is deactivated - i.e. the mode is unselected + def deactivated + self._clear_marker + RBA::MainWindow.instance.message("", 0) + end + + # overloaded callback: + # a mouse button was clicked + + 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._update_box(RBA::DBox::new(@start_point, p)) + self._finish + end + + return true + + end + + return false + + end + + # overloaded callback: + # the mouse was moved + 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, :visualize => true) + self.add_mouse_cursor(p) + else + self.clear_mouse_cursors + p = self.snap2(p, @start_point, true, self.ac_from_buttons(buttons), :visualize => 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". +# The 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 + + # Called to create the configuration pages + def create_config_pages + self.add_config_page(DragBoxConfigPage::new) + end + + # Called to create the editor options pages + def create_editor_options_pages + self.add_editor_options_page(DragBoxEditorOptionsPage::new) + self.add_editor_options_page(DragBoxFocusPage::new) + end + + # Creates the plugin + def create_plugin(manager, root, view) + return DragBoxPlugin::new(view) + end + +end + +# Creates the singleton instance - as we register it, +# it is not garbage collected +DragBoxPluginFactory::new + +end + + diff --git a/src/lay/lay/macro_templates/drag_box_sample_python.lym b/src/lay/lay/macro_templates/drag_box_sample_python.lym new file mode 100644 index 000000000..7edd306f2 --- /dev/null +++ b/src/lay/lay/macro_templates/drag_box_sample_python.lym @@ -0,0 +1,443 @@ + + + A plugin sample\nThis sample provides a box drawing feature and demonstrates UI components and snapping + general + true + false + false + + python + # 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() + + diff --git a/src/lay/lay/macro_templates/index.txt b/src/lay/lay/macro_templates/index.txt index 57c7b9c2c..6ff3d005f 100644 --- a/src/lay/lay/macro_templates/index.txt +++ b/src/lay/lay/macro_templates/index.txt @@ -25,6 +25,7 @@ editor_hooks_sample.lym qt_designer.lym qt_dialog.lym qt_server.lym +drag_box_sample.lym [pymacros] # General group @@ -45,4 +46,5 @@ editor_hooks_sample_python.lym qt_designer_python.lym qt_dialog_python.lym qt_server_python.lym +drag_box_sample_python.lym diff --git a/src/laybasic/laybasic/gsiDeclLayDispatcher.cc b/src/laybasic/laybasic/gsiDeclLayDispatcher.cc new file mode 100644 index 000000000..36e2d11a8 --- /dev/null +++ b/src/laybasic/laybasic/gsiDeclLayDispatcher.cc @@ -0,0 +1,139 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "gsiDecl.h" +#include "gsiDeclBasic.h" +#include "layDispatcher.h" + +namespace gsi +{ + +static std::vector +get_config_names (lay::Dispatcher *dispatcher) +{ + std::vector names; + dispatcher->get_config_names (names); + return names; +} + +static lay::Dispatcher *dispatcher_instance () +{ + return lay::Dispatcher::instance (); +} + +static tl::Variant get_config (lay::Dispatcher *dispatcher, const std::string &name) +{ + std::string value; + if (dispatcher->config_get (name, value)) { + return tl::Variant (value); + } else { + return tl::Variant (); + } +} + +/** + * @brief Exposes the Dispatcher interface + * + * This interface is intentionally not derived from Plugin. It is used currently to + * identify the dispatcher node for configuration. The Plugin nature of this interface + * is somewhat artificial and may be removed later. + * + * TODO: this is a duplicate of the respective methods in LayoutView and Application. + * This is intentional since we don't want to spend the only derivation path on this. + * Once there is a mixin concept, provide a path through that concept. + */ +Class decl_Dispatcher ("lay", "Dispatcher", + method ("clear_config", &lay::Dispatcher::clear_config, + "@brief Clears the configuration parameters\n" + ) + + method ("instance", &dispatcher_instance, + "@brief Gets the singleton instance of the Dispatcher object\n" + "\n" + "@return The instance\n" + ) + + method ("write_config", &lay::Dispatcher::write_config, gsi::arg ("file_name"), + "@brief Writes configuration to a file\n" + "@return A value indicating whether the operation was successful\n" + "\n" + "If the configuration file cannot be written, false \n" + "is returned but no exception is thrown.\n" + ) + + method ("read_config", &lay::Dispatcher::read_config, gsi::arg ("file_name"), + "@brief Reads the configuration from a file\n" + "@return A value indicating whether the operation was successful\n" + "\n" + "This method silently does nothing, if the config file does not\n" + "exist. If it does and an error occurred, the error message is printed\n" + "on stderr. In both cases, false is returned.\n" + ) + + method_ext ("get_config", &get_config, gsi::arg ("name"), + "@brief Gets the value of a local configuration parameter\n" + "\n" + "@param name The name of the configuration parameter whose value shall be obtained (a string)\n" + "\n" + "@return The value of the parameter or nil if there is no such parameter\n" + ) + + method ("set_config", (void (lay::Dispatcher::*) (const std::string &, const std::string &)) &lay::Dispatcher::config_set, gsi::arg ("name"), gsi::arg ("value"), + "@brief Set a local configuration parameter with the given name to the given value\n" + "\n" + "@param name The name of the configuration parameter to set\n" + "@param value The value to which to set the configuration parameter\n" + "\n" + "This method sets a configuration parameter with the given name to the given value. " + "Values can only be strings. Numerical values have to be converted into strings first. " + "Local configuration parameters override global configurations for this specific view. " + "This allows for example to override global settings of background colors. " + "Any local settings are not written to the configuration file. " + ) + + method_ext ("get_config_names", &get_config_names, + "@brief Gets the configuration parameter names\n" + "\n" + "@return A list of configuration parameter names\n" + "\n" + "This method returns the names of all known configuration parameters. These names can be used to " + "get and set configuration parameter values.\n" + ) + + method ("commit_config", &lay::Dispatcher::config_end, + "@brief Commits the configuration settings\n" + "\n" + "Some configuration options are queued for performance reasons and become active only after 'commit_config' has been called. " + "After a sequence of \\set_config calls, this method should be called to activate the " + "settings made by these calls.\n" + ), + "@brief Root of the configuration space in the plugin context and menu dispatcher\n" + "\n" + "This class provides access to the root configuration space in the context " + "of plugin programming. You can use this class to obtain configuration parameters " + "from the configuration tree during plugin initialization. However, the " + "preferred way of plugin configuration is through \\Plugin#configure.\n" + "\n" + "Currently, the application object provides an identical entry point for configuration modification. " + "For example, \"Application::instance.set_config\" is identical to \"Dispatcher::instance.set_config\". " + "Hence there is little motivation for the Dispatcher class currently and " + "this interface may be modified or removed in the future." + "\n" + "This class has been introduced in version 0.25 as 'PluginRoot'.\n" + "It is renamed and enhanced as 'Dispatcher' in 0.27." +); + +} diff --git a/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc b/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc index 15925208b..86892f3c1 100644 --- a/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc +++ b/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc @@ -144,6 +144,11 @@ static std::string get_line_style (lay::LayoutViewBase *view, unsigned int index return view->line_styles ().style (index).to_string (); } +static std::string layer_list_name (lay::LayoutViewBase *view, unsigned int index) +{ + return view->get_properties (index).name (); +} + static void transaction (lay::LayoutViewBase *view, const std::string &desc) { view->manager ()->transaction (desc); @@ -358,14 +363,6 @@ static QWidget *widget (lay::LayoutViewBase *view) #endif -static std::vector -get_config_names (lay::LayoutViewBase *view) -{ - std::vector names; - view->get_config_names (names); - return names; -} - static void send_key_press_event (lay::LayoutViewBase *view, unsigned int key, unsigned int buttons) { @@ -497,7 +494,9 @@ static bool view_is_dirty (lay::LayoutViewBase *view) return view->is_dirty (); } -LAYBASIC_PUBLIC Class decl_LayoutViewBase ("lay", "LayoutViewBase", +extern Class decl_Dispatcher; + +LAYBASIC_PUBLIC Class decl_LayoutViewBase (decl_Dispatcher, "lay", "LayoutViewBase", gsi::constant ("LV_NoLayers", (unsigned int) lay::LayoutViewBase::LV_NoLayers, "@brief With this option, no layers view will be provided (see \\layer_control_frame)\n" "Use this value with the constructor's 'options' argument.\n" @@ -1572,6 +1571,10 @@ LAYBASIC_PUBLIC Class decl_LayoutViewBase ("lay", "LayoutVi "@brief Sets the title of the given layer properties tab\n" "This method has been introduced in version 0.21.\n" ) + + gsi::method_ext ("layer_list_name", &layer_list_name, gsi::arg ("index"), + "@brief Gets the title of the given layer properties tab\n" + "This method has been introduced in version 0.30.4.\n" + ) + gsi::method_ext ("remove_stipple", &remove_stipple, gsi::arg ("index"), "@brief Removes the stipple pattern with the given index\n" "The pattern with an index less than the first custom pattern cannot be removed. " @@ -1940,68 +1943,6 @@ LAYBASIC_PUBLIC Class decl_LayoutViewBase ("lay", "LayoutVi "\n" "This method has been added in version 0.26." ) + - // HINT: the cast is important to direct GSI to the LayoutView method rather than the - // Plugin method (in which case we get a segmentation violation ..) - // TODO: this method belongs to the Plugin interface and should be located there. - // Change this once there is a mixin concept available and the Plugin interface can - // be mixed into LayoutView. - gsi::method ("clear_config", (void (lay::LayoutViewBase::*)()) &lay::LayoutViewBase::clear_config, - "@brief Clears the local configuration parameters\n" - "\n" - "See \\set_config for a description of the local configuration parameters." - ) + - // TODO: this method belongs to the Plugin interface and should be located there. - // Change this once there is a mixin concept available and the Plugin interface can - // be mixed into LayoutView. - gsi::method_ext ("get_config_names", &get_config_names, - "@brief Gets the configuration parameter names\n" - "\n" - "@return A list of configuration parameter names\n" - "\n" - "This method returns the names of all known configuration parameters. These names can be used to " - "get and set configuration parameter values.\n" - "\n" - "This method was introduced in version 0.25.\n" - ) + - // TODO: this method belongs to the Plugin interface and should be located there. - // Change this once there is a mixin concept available and the Plugin interface can - // be mixed into LayoutView. - gsi::method ("get_config", (std::string (lay::LayoutViewBase::*)(const std::string &name) const) &lay::LayoutViewBase::config_get, gsi::arg ("name"), - "@brief Gets the value of a local configuration parameter\n" - "\n" - "@param name The name of the configuration parameter whose value shall be obtained (a string)\n" - "\n" - "@return The value of the parameter\n" - "\n" - "See \\set_config for a description of the local configuration parameters." - ) + - // TODO: this method belongs to the Plugin interface and should be located there. - // Change this once there is a mixin concept available and the Plugin interface can - // be mixed into LayoutView. - gsi::method ("set_config", (void (lay::LayoutViewBase::*)(const std::string &name, const std::string &value)) &lay::LayoutViewBase::config_set, gsi::arg ("name"), gsi::arg ("value"), - "@brief Sets a local configuration parameter with the given name to the given value\n" - "\n" - "@param name The name of the configuration parameter to set\n" - "@param value The value to which to set the configuration parameter\n" - "\n" - "This method sets a local configuration parameter with the given name to the given value. " - "Values can only be strings. Numerical values have to be converted into strings first. " - "Local configuration parameters override global configurations for this specific view. " - "This allows for example to override global settings of background colors. " - "Any local settings are not written to the configuration file. " - ) + - // TODO: this method belongs to the Plugin interface and should be located there. - // Change this once there is a mixin concept available and the Plugin interface can - // be mixed into LayoutView. - gsi::method ("commit_config", (void (lay::LayoutViewBase::*)()) &lay::LayoutViewBase::config_end, - "@brief Commits the configuration settings\n" - "\n" - "Some configuration options are queued for performance reasons and become active only after 'commit_config' has been called. " - "After a sequence of \\set_config calls, this method should be called to activate the " - "settings made by these calls.\n" - "\n" - "This method has been introduced in version 0.25.\n" - ) + gsi::method_ext ("transaction", &gsi::transaction, gsi::arg ("description"), "@brief Begins a transaction\n" "\n" diff --git a/src/laybasic/laybasic/gsiDeclLayPlugin.cc b/src/laybasic/laybasic/gsiDeclLayPlugin.cc deleted file mode 100644 index e35b72631..000000000 --- a/src/laybasic/laybasic/gsiDeclLayPlugin.cc +++ /dev/null @@ -1,1075 +0,0 @@ - -/* - - KLayout Layout Viewer - Copyright (C) 2006-2025 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 "gsiDecl.h" -#include "gsiDeclBasic.h" -#include "layPlugin.h" -#include "layViewObject.h" -#include "layLayoutViewBase.h" -#include "layCursor.h" - -namespace gsi -{ - class PluginFactoryBase; - class PluginBase; - -// TODO: these static variables are a bad hack! -// However it's not easy to pass parameters to a C++ classes constructor in Ruby without -// compromising the capability to derive from that class (at least I have not learned how -// to create a "new" in the super class *and* allow a "new" of the derived class). Anyway, -// since PluginBase object are only allowed to be created inside the create_plugin method -// of the factory, this hack is a quick but dirty workaround. -static bool s_in_create_plugin = false; -static lay::LayoutViewBase *sp_view = 0; -static lay::Dispatcher *sp_dispatcher = 0; - -class PluginBase - : public lay::Plugin, public lay::ViewService -{ -public: - PluginBase () - : lay::Plugin (sp_dispatcher), lay::ViewService (sp_view ? sp_view->canvas () : 0) - { - if (! s_in_create_plugin) { - throw tl::Exception (tl::to_string (tr ("A PluginBase object can only be created in the PluginFactory's create_plugin method"))); - } - } - - void grab_mouse () - { - if (ui ()) { - ui ()->grab_mouse (this, false); - } - } - - void ungrab_mouse () - { - if (ui ()) { - ui ()->ungrab_mouse (this); - } - } - - void set_cursor (int c) - { - if (ui ()) { - lay::ViewService::set_cursor ((enum lay::Cursor::cursor_shape) c); - } - } - - virtual lay::ViewService *view_service_interface () - { - return this; - } - - virtual void menu_activated (const std::string &symbol) - { - if (f_menu_activated.can_issue ()) { - f_menu_activated.issue (&lay::Plugin::menu_activated, symbol); - } else { - lay::Plugin::menu_activated (symbol); - } - } - - virtual bool configure (const std::string &name, const std::string &value) - { - return f_configure.can_issue () ? f_configure.issue (&PluginBase::configure, name, value) : lay::Plugin::configure (name, value); - } - - virtual void config_finalize () - { - f_config_finalize.can_issue () ? f_config_finalize.issue (&PluginBase::config_finalize) : lay::Plugin::config_finalize (); - } - - virtual bool key_event (unsigned int key, unsigned int buttons) - { - if (f_key_event.can_issue ()) { - return f_key_event.issue (&lay::ViewService::key_event, key, buttons); - } else { - return lay::ViewService::key_event (key, buttons); - } - } - - virtual bool mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio) - { - if (f_mouse_press_event.can_issue ()) { - return f_mouse_press_event.issue (&PluginBase::mouse_press_event_noref, p, buttons, prio); - } else { - return lay::ViewService::mouse_press_event (p, buttons, prio); - } - } - - // NOTE: this version doesn't take a point reference which allows up to store the point - bool mouse_press_event_noref (db::DPoint p, unsigned int buttons, bool prio) - { - return mouse_press_event (p, buttons, prio); - } - - virtual bool mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio) - { - if (f_mouse_click_event.can_issue ()) { - return f_mouse_click_event.issue (&PluginBase::mouse_click_event_noref, p, buttons, prio); - } else { - return lay::ViewService::mouse_click_event (p, buttons, prio); - } - } - - // NOTE: this version doesn't take a point reference which allows up to store the point - bool mouse_click_event_noref (db::DPoint p, unsigned int buttons, bool prio) - { - return mouse_click_event (p, buttons, prio); - } - - virtual bool mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio) - { - if (f_mouse_double_click_event.can_issue ()) { - return f_mouse_double_click_event.issue (&PluginBase::mouse_double_click_event_noref, p, buttons, prio); - } else { - return lay::ViewService::mouse_double_click_event (p, buttons, prio); - } - } - - // NOTE: this version doesn't take a point reference which allows up to store the point - bool mouse_double_click_event_noref (db::DPoint p, unsigned int buttons, bool prio) - { - return mouse_double_click_event (p, buttons, prio); - } - - virtual bool leave_event (bool prio) - { - if (f_leave_event.can_issue ()) { - return f_leave_event.issue (&lay::ViewService::leave_event, prio); - } else { - return lay::ViewService::leave_event (prio); - } - } - - virtual bool enter_event (bool prio) - { - if (f_enter_event.can_issue ()) { - return f_enter_event.issue (&lay::ViewService::enter_event, prio); - } else { - return lay::ViewService::enter_event (prio); - } - } - - virtual bool mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) - { - if (f_mouse_move_event.can_issue ()) { - return f_mouse_move_event.issue (&PluginBase::mouse_move_event_noref, p, buttons, prio); - } else { - return lay::ViewService::mouse_move_event (p, buttons, prio); - } - } - - // NOTE: this version doesn't take a point reference which allows up to store the point - bool mouse_move_event_noref (db::DPoint p, unsigned int buttons, bool prio) - { - return mouse_move_event (p, buttons, prio); - } - - virtual bool mouse_release_event (const db::DPoint &p, unsigned int buttons, bool prio) - { - if (f_mouse_release_event.can_issue ()) { - return f_mouse_release_event.issue (&PluginBase::mouse_release_event_noref, p, buttons, prio); - } else { - return lay::ViewService::mouse_release_event (p, buttons, prio); - } - } - - // NOTE: this version doesn't take a point reference which allows up to store the point - bool mouse_release_event_noref (db::DPoint p, unsigned int buttons, bool prio) - { - return mouse_release_event (p, buttons, prio); - } - - virtual bool wheel_event (int delta, bool horizontal, const db::DPoint &p, unsigned int buttons, bool prio) - { - if (f_wheel_event.can_issue ()) { - return f_wheel_event.issue (&PluginBase::wheel_event_noref, delta, horizontal, p, buttons, prio); - } else { - return lay::ViewService::wheel_event (delta, horizontal, p, buttons, prio); - } - } - - // NOTE: this version doesn't take a point reference which allows up to store the point - bool wheel_event_noref (int delta, bool horizontal, db::DPoint p, unsigned int buttons, bool prio) - { - return wheel_event (delta, horizontal, p, buttons, prio); - } - - virtual void activated () - { - if (f_activated.can_issue ()) { - f_activated.issue (&lay::ViewService::activated); - } else { - lay::ViewService::activated (); - } - } - - virtual void deactivated () - { - if (f_deactivated.can_issue ()) { - f_deactivated.issue (&lay::ViewService::deactivated); - } else { - lay::ViewService::deactivated (); - } - } - - virtual void drag_cancel () - { - if (f_drag_cancel.can_issue ()) { - f_drag_cancel.issue (&lay::ViewService::drag_cancel); - } else { - lay::ViewService::drag_cancel (); - } - } - - virtual void update () - { - if (f_update.can_issue ()) { - f_update.issue (&lay::ViewService::update); - } else { - lay::ViewService::update (); - } - } - - virtual bool has_tracking_position () const - { - if (f_has_tracking_position.can_issue ()) { - return f_has_tracking_position.issue (&lay::ViewService::has_tracking_position); - } else { - return lay::ViewService::has_tracking_position (); - } - } - - virtual db::DPoint tracking_position () const - { - if (f_tracking_position.can_issue ()) { - return f_tracking_position.issue (&lay::ViewService::tracking_position); - } else { - return lay::ViewService::tracking_position (); - } - } - - gsi::Callback f_menu_activated; - gsi::Callback f_configure; - gsi::Callback f_config_finalize; - gsi::Callback f_key_event; - gsi::Callback f_mouse_press_event; - gsi::Callback f_mouse_click_event; - gsi::Callback f_mouse_double_click_event; - gsi::Callback f_leave_event; - gsi::Callback f_enter_event; - gsi::Callback f_mouse_move_event; - gsi::Callback f_mouse_release_event; - gsi::Callback f_wheel_event; - gsi::Callback f_activated; - gsi::Callback f_deactivated; - gsi::Callback f_drag_cancel; - gsi::Callback f_update; - gsi::Callback f_has_tracking_position; - gsi::Callback f_tracking_position; -}; - -static std::map s_factories; - -class PluginFactoryBase - : public lay::PluginDeclaration -{ -public: - PluginFactoryBase () - : PluginDeclaration (), - m_implements_mouse_mode (true), mp_registration (0) - { - // .. nothing yet .. - } - - ~PluginFactoryBase () - { - for (auto f = s_factories.begin (); f != s_factories.end (); ++f) { - if (f->second == this) { - s_factories.erase (f); - break; - } - } - - delete mp_registration; - mp_registration = 0; - } - - void register_gsi (int position, const char *name, const char *title) - { - register_gsi2 (position, name, title, 0); - } - - void register_gsi2 (int position, const char *name, const char *title, const char *icon) - { - // makes the object owned by the C++ side - keep (); - - // remove an existing factory with the same name - std::map ::iterator f = s_factories.find (name); - if (f != s_factories.end ()) { - delete f->second; - f->second = this; - } else { - s_factories.insert (std::make_pair (std::string (name), this)); - } - - // cancel any previous registration and register (again) - delete mp_registration; - mp_registration = new tl::RegisteredClass (this, position, name, false /*does not own object*/); - - m_mouse_mode_title = name; - if (title) { - m_mouse_mode_title += "\t"; - m_mouse_mode_title += title; - } - if (icon) { - m_mouse_mode_title += "\t<"; - m_mouse_mode_title += icon; - m_mouse_mode_title += ">"; - } - - // (dynamically) register the plugin class. This will also call initialize if the main window is - // present already. - register_plugin (); - } - - virtual bool configure (const std::string &name, const std::string &value) - { - if (f_configure.can_issue ()) { - return f_configure.issue (&lay::PluginDeclaration::configure, name, value); - } else { - return lay::PluginDeclaration::configure (name, value); - } - } - - virtual void config_finalize () - { - if (f_config_finalize.can_issue ()) { - f_config_finalize.issue (&lay::PluginDeclaration::config_finalize); - } else { - lay::PluginDeclaration::config_finalize (); - } - } - - virtual bool menu_activated (const std::string &symbol) const - { - if (f_menu_activated.can_issue ()) { - return f_menu_activated.issue (&lay::PluginDeclaration::menu_activated, symbol); - } else { - return lay::PluginDeclaration::menu_activated (symbol); - } - } - - virtual void initialize (lay::Dispatcher *root) - { - if (f_initialize.can_issue ()) { - f_initialize.issue (&lay::PluginDeclaration::initialize, root); - } else { - lay::PluginDeclaration::initialize (root); - } - } - - virtual void uninitialize (lay::Dispatcher *root) - { - if (f_uninitialize.can_issue ()) { - f_uninitialize.issue (&lay::PluginDeclaration::uninitialize, root); - } else { - lay::PluginDeclaration::uninitialize (root); - } - } - - virtual lay::Plugin *create_plugin (db::Manager *manager, lay::Dispatcher *root, lay::LayoutViewBase *view) const - { - if (f_create_plugin.can_issue ()) { - return create_plugin_gsi (manager, root, view); - } else { - return lay::PluginDeclaration::create_plugin (manager, root, view); - } - } - - virtual gsi::PluginBase *create_plugin_gsi (db::Manager *manager, lay::Dispatcher *root, lay::LayoutViewBase *view) const - { - // TODO: this is a hack. See notes above at s_in_create_plugin - s_in_create_plugin = true; - sp_view = view; - sp_dispatcher = root; - gsi::PluginBase *ret = 0; - try { - ret = f_create_plugin.issue (&PluginFactoryBase::create_plugin_gsi, manager, root, view); - s_in_create_plugin = false; - sp_view = 0; - sp_dispatcher = 0; - } catch (...) { - s_in_create_plugin = false; - sp_view = 0; - sp_dispatcher = 0; - } - - return ret; - } - - virtual void get_menu_entries (std::vector &menu_entries) const - { - menu_entries = m_menu_entries; - } - - virtual void get_options (std::vector < std::pair > &options) const - { - options = m_options; - } - - void add_menu_entry1 (const std::string &menu_name, const std::string &insert_pos) - { - m_menu_entries.push_back (lay::separator (menu_name, insert_pos)); - } - - void add_menu_entry2 (const std::string &symbol, const std::string &menu_name, const std::string &insert_pos, const std::string &title) - { - m_menu_entries.push_back (lay::menu_item (symbol, menu_name, insert_pos, title)); - } - - void add_menu_entry_copy (const std::string &symbol, const std::string &menu_name, const std::string &insert_pos, const std::string ©_from) - { - m_menu_entries.push_back (lay::menu_item_copy (symbol, menu_name, insert_pos, copy_from)); - } - - void add_submenu (const std::string &menu_name, const std::string &insert_pos, const std::string &title) - { - m_menu_entries.push_back (lay::submenu (menu_name, insert_pos, title)); - } - - void add_config_menu_item (const std::string &menu_name, const std::string &insert_pos, const std::string &title, const std::string &cname, const std::string &cvalue) - { - m_menu_entries.push_back (lay::config_menu_item (menu_name, insert_pos, title, cname, cvalue)); - } - - void add_menu_entry3 (const std::string &symbol, const std::string &menu_name, const std::string &insert_pos, const std::string &title, bool sub_menu) - { - if (sub_menu) { - m_menu_entries.push_back (lay::submenu (symbol, menu_name, insert_pos, title)); - } else { - m_menu_entries.push_back (lay::menu_item (symbol, menu_name, insert_pos, title)); - } - } - - void add_option (const std::string &name, const std::string &default_value) - { - m_options.push_back (std::make_pair (name, default_value)); - } - - void has_tool_entry (bool f) - { - m_implements_mouse_mode = f; - } - - virtual bool implements_mouse_mode (std::string &title) const - { - title = m_mouse_mode_title; - return m_implements_mouse_mode; - } - - gsi::Callback f_create_plugin; - gsi::Callback f_initialize; - gsi::Callback f_uninitialize; - gsi::Callback f_configure; - gsi::Callback f_config_finalize; - gsi::Callback f_menu_activated; - -private: - std::vector > m_options; - std::vector m_menu_entries; - bool m_implements_mouse_mode; - std::string m_mouse_mode_title; - tl::RegisteredClass *mp_registration; -}; - -Class decl_PluginFactory ("lay", "PluginFactory", - method ("register", &PluginFactoryBase::register_gsi, gsi::arg ("position"), gsi::arg ("name"), gsi::arg ("title"), - "@brief Registers the plugin factory\n" - "@param position An integer that determines the order in which the plugins are created. The internal plugins use the values from 1000 to 50000.\n" - "@param name The plugin name. This is an arbitrary string which should be unique. Hence it is recommended to use a unique prefix, i.e. \"myplugin::ThePluginClass\".\n" - "@param title The title string which is supposed to appear in the tool bar and menu related to this plugin.\n" - "\n" - "Registration of the plugin factory makes the object known to the system. Registration requires that the menu items have been set " - "already. Hence it is recommended to put the registration at the end of the initialization method of the factory class.\n" - ) + - method ("register", &PluginFactoryBase::register_gsi2, gsi::arg ("position"), gsi::arg ("name"), gsi::arg ("title"), gsi::arg ("icon"), - "@brief Registers the plugin factory\n" - "@param position An integer that determines the order in which the plugins are created. The internal plugins use the values from 1000 to 50000.\n" - "@param name The plugin name. This is an arbitrary string which should be unique. Hence it is recommended to use a unique prefix, i.e. \"myplugin::ThePluginClass\".\n" - "@param title The title string which is supposed to appear in the tool bar and menu related to this plugin.\n" - "@param icon The path to the icon that appears in the tool bar and menu related to this plugin.\n" - "\n" - "This version also allows registering an icon for the tool bar.\n" - "\n" - "Registration of the plugin factory makes the object known to the system. Registration requires that the menu items have been set " - "already. Hence it is recommended to put the registration at the end of the initialization method of the factory class.\n" - ) + - callback ("configure", &gsi::PluginFactoryBase::configure, &gsi::PluginFactoryBase::f_configure, gsi::arg ("name"), gsi::arg ("value"), - "@brief Gets called for configuration events for the plugin singleton\n" - "This method can be reimplemented to receive configuration events " - "for the plugin singleton. Before a configuration can be received it must be " - "registered by calling \\add_option in the plugin factories' constructor.\n" - "\n" - "The implementation of this method may return true indicating that the configuration request " - "will not be handled by further modules. It's more cooperative to return false which will " - "make the system distribute the configuration request to other receivers as well.\n" - "\n" - "@param name The configuration key\n" - "@param value The value of the configuration variable\n" - "@return True to stop further processing\n" - ) + - callback ("config_finalize", &gsi::PluginFactoryBase::config_finalize, &gsi::PluginFactoryBase::f_config_finalize, - "@brief Gets called after a set of configuration events has been sent\n" - "This method can be reimplemented and is called after a set of configuration events " - "has been sent to the plugin factory singleton with \\configure. It can be used to " - "set up user interfaces properly for example.\n" - ) + - callback ("menu_activated", &gsi::PluginFactoryBase::menu_activated, &gsi::PluginFactoryBase::f_menu_activated, gsi::arg ("symbol"), - "@brief Gets called when a menu item is selected\n" - "\n" - "Usually, menu-triggered functionality is implemented in the per-view instance of the plugin. " - "However, using this method it is possible to implement functionality globally for all plugin " - "instances. The symbol is the string registered with the specific menu item in the \\add_menu_item " - "call.\n" - "\n" - "If this method was handling the menu event, it should return true. This indicates that the event " - "will not be propagated to other plugins hence avoiding duplicate calls.\n" - ) + - callback ("initialized", &gsi::PluginFactoryBase::initialize, &gsi::PluginFactoryBase::f_initialize, gsi::arg ("dispatcher"), - "@brief Gets called when the plugin singleton is initialized, i.e. when the application has been started.\n" - "@param dispatcher The reference to the \\MainWindow object\n" - ) + - callback ("uninitialized", &gsi::PluginFactoryBase::uninitialize, &gsi::PluginFactoryBase::f_uninitialize, gsi::arg ("dispatcher"), - "@brief Gets called when the application shuts down and the plugin is unregistered\n" - "This event can be used to free resources allocated with this factory singleton.\n" - "@param dispatcher The reference to the \\MainWindow object\n" - ) + - factory_callback ("create_plugin", &gsi::PluginFactoryBase::create_plugin_gsi, &gsi::PluginFactoryBase::f_create_plugin, gsi::arg ("manager"), gsi::arg ("dispatcher"), gsi::arg ("view"), - "@brief Creates the plugin\n" - "This is the basic functionality that the factory must provide. This method must create a plugin of the " - "specific type.\n" - "@param manager The database manager object responsible for handling database transactions\n" - "@param dispatcher The reference to the \\MainWindow object\n" - "@param view The \\LayoutView that is plugin is created for\n" - "@return The new \\Plugin implementation object\n" - ) + - method ("add_menu_entry", &gsi::PluginFactoryBase::add_menu_entry1, gsi::arg ("menu_name"), gsi::arg ("insert_pos"), - "@brief Specifies a separator\n" - "Call this method in the factory constructor to build the menu items that this plugin shall create.\n" - "This specific call inserts a separator at the given position (insert_pos). The position uses abstract menu item paths " - "and \"menu_name\" names the component that will be created. See \\AbstractMenu for a description of the path.\n" - ) + - method ("add_menu_entry", &gsi::PluginFactoryBase::add_menu_entry2, gsi::arg ("symbol"), gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), - "@brief Specifies a menu item\n" - "Call this method in the factory constructor to build the menu items that this plugin shall create.\n" - "This specific call inserts a menu item at the specified position (insert_pos). The position uses abstract menu item paths " - "and \"menu_name\" names the component that will be created. See \\AbstractMenu for a description of the path.\n" - "When the menu item is selected \"symbol\" is the string that is sent to the \\menu_activated callback (either the global one for the factory ot the one of the per-view plugin instance).\n" - "\n" - "@param symbol The string to send to the plugin if the menu is triggered\n" - "@param menu_name The name of entry to create at the given position\n" - "@param insert_pos The position where to create the entry\n" - "@param title The title string for the item. The title can contain a keyboard shortcut in round braces after the title text, i.e. \"My Menu Item(F12)\"\n" - ) + - method ("#add_menu_entry", &gsi::PluginFactoryBase::add_menu_entry3, gsi::arg ("symbol"), gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), gsi::arg ("sub_menu"), - "@brief Specifies a menu item or sub-menu\n" - "Similar to the previous form of \"add_menu_entry\", but this version allows also to create sub-menus by setting the " - "last parameter to \"true\".\n" - "\n" - "With version 0.27 it's more convenient to use \\add_submenu." - ) + - method ("add_menu_item_clone", &gsi::PluginFactoryBase::add_menu_entry_copy, gsi::arg ("symbol"), gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("copy_from"), - "@brief Specifies a menu item as a clone of another one\n" - "Using this method, a menu item can be made a clone of another entry (given as path by 'copy_from').\n" - "The new item will share the \\Action object with the original one, so manipulating the action will change both the original entry " - "and the new entry.\n" - "\n" - "This method has been introduced in version 0.27." - ) + - method ("add_submenu", &gsi::PluginFactoryBase::add_submenu, gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), - "@brief Specifies a menu item or sub-menu\n" - "\n" - "This method has been introduced in version 0.27." - ) + - method ("add_config_menu_item", &gsi::PluginFactoryBase::add_config_menu_item, gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), gsi::arg ("cname"), gsi::arg ("cvalue"), - "@brief Adds a configuration menu item\n" - "\n" - "Menu items created this way will send a configuration request with 'cname' as the configuration parameter name " - "and 'cvalue' as the configuration parameter value.\n" - "\n" - "This method has been introduced in version 0.27." - ) + - method ("add_option", &gsi::PluginFactoryBase::add_option, gsi::arg ("name"), gsi::arg ("default_value"), - "@brief Specifies configuration variables.\n" - "Call this method in the factory constructor to add configuration key/value pairs to the configuration repository. " - "Without specifying configuration variables, the status of a plugin cannot be persisted. " - "\n\n" - "Once the configuration variables are known, they can be retrieved on demand using \"get_config\" from " - "\\MainWindow or listening to \\configure callbacks (either in the factory or the plugin instance). Configuration variables can " - "be set using \"set_config\" from \\MainWindow. This scheme also works without registering the configuration options, but " - "doing so has the advantage that it is guaranteed that a variable with this keys exists and has the given default value initially." - "\n\n" - ) + - method ("has_tool_entry=", &gsi::PluginFactoryBase::has_tool_entry, gsi::arg ("f"), - "@brief Enables or disables the tool bar entry\n" - "Initially this property is set to true. This means that the plugin will have a visible entry in the toolbar. " - "This property can be set to false to disable this feature. In that case, the title and icon given on registration will be ignored. " - ), - "@brief The plugin framework's plugin factory object\n" - "\n" - "Plugins are components that extend KLayout's functionality in various aspects. Scripting support exists " - "currently for providing mouse mode handlers and general on-demand functionality connected with a menu " - "entry.\n" - "\n" - "Plugins are objects that implement the \\Plugin interface. Each layout view is associated with one instance " - "of such an object. The PluginFactory is a singleton which is responsible for creating \\Plugin objects and " - "providing certain configuration information such as where to put the menu items connected to this plugin and " - "what configuration keys are used.\n" - "\n" - "An implementation of PluginFactory must at least provide an implementation of \\create_plugin. This method " - "must instantiate a new object of the specific plugin.\n" - "\n" - "After the factory has been created, it must be registered in the system using one of the \\register methods. " - "It is therefore recommended to put the call to \\register at the end of the \"initialize\" method. For the registration " - "to work properly, the menu items must be defined before \\register is called.\n" - "\n" - "The following features can also be implemented:\n" - "\n" - "@
    \n" - " @
  • Reserve keys in the configuration file using \\add_option in the constructor@
  • \n" - " @
  • Create menu items by using \\add_menu_entry in the constructor@
  • \n" - " @
  • Set the title for the mode entry that appears in the tool bar using the \\register argument@
  • \n" - " @
  • Provide global functionality (independent from the layout view) using \\configure or \\menu_activated@
  • \n" - "@
\n" - "\n" - "This is a simple example for a plugin in Ruby. It switches the mouse cursor to a 'cross' cursor when it is active:\n" - "\n" - "@code\n" - "class PluginTestFactory < RBA::PluginFactory\n" - "\n" - " # Constructor\n" - " def initialize\n" - " # registers the new plugin class at position 100000 (at the end), with name\n" - " # \"my_plugin_test\" and title \"My plugin test\"\n" - " register(100000, \"my_plugin_test\", \"My plugin test\")\n" - " end\n" - " \n" - " # Create a new plugin instance of the custom type\n" - " def create_plugin(manager, dispatcher, view)\n" - " return PluginTest.new\n" - " end\n" - "\n" - "end\n" - "\n" - "# The plugin class\n" - "class PluginTest < RBA::Plugin\n" - " def mouse_moved_event(p, buttons, prio)\n" - " if prio\n" - " # Set the cursor to cross if our plugin is active.\n" - " set_cursor(RBA::Cursor::Cross)\n" - " end\n" - " # Returning false indicates that we don't want to consume the event.\n" - " # This way for example the cursor position tracker still works.\n" - " false\n" - " end\n" - " def mouse_click_event(p, buttons, prio)\n" - " if prio\n" - " puts \"mouse button clicked.\"\n" - " # This indicates we want to consume the event and others don't receive the mouse click\n" - " # with prio = false.\n" - " return true\n" - " end\n" - " # don't consume the event if we are not active.\n" - " false\n" - " end\n" - "end\n" - "\n" - "# Instantiate the new plugin factory.\n" - "PluginTestFactory.new\n" - "@/code\n" - "\n" - "This class has been introduced in version 0.22.\n" -); - -Class decl_Plugin ("lay", "Plugin", - callback ("menu_activated", &gsi::PluginBase::menu_activated, &gsi::PluginBase::f_menu_activated, gsi::arg ("symbol"), - "@brief Gets called when a custom menu item is selected\n" - "When a menu item is clicked which was registered with the plugin factory, the plugin's 'menu_activated' method is " - "called for the current view. The symbol registered for the menu item is passed in the 'symbol' argument." - ) + - callback ("configure", &gsi::PluginBase::configure, &gsi::PluginBase::f_configure, gsi::arg ("name"), gsi::arg ("value"), - "@brief Sends configuration requests to the plugin\n" - "@param name The name of the configuration variable as registered in the plugin factory\n" - "@param value The value of the configuration variable\n" - "When a configuration variable is changed, the new value is reported to the plugin by calling the 'configure' method." - ) + - callback ("config_finalize", &gsi::PluginBase::config_finalize, &gsi::PluginBase::f_config_finalize, - "@brief Sends the post-configuration request to the plugin\n" - "After all configuration parameters have been sent, 'config_finalize' is called to given the plugin a chance to " - "update its internal state according to the new configuration.\n" - ) + - callback ("key_event", &gsi::PluginBase::key_event, &gsi::PluginBase::f_key_event, gsi::arg ("key"), gsi::arg ("buttons"), - "@brief Handles the key pressed event\n" - "This method will called by the view on the active plugin when a button is pressed on the mouse.\n" - "\n" - "If the plugin handles the event, it should return true to indicate that the event should not be processed further." - "\n" - "@param key The Qt key code of the key that was pressed\n" - "@param buttons A combination of the constants in the \\ButtonState class which codes both the mouse buttons and the key modifiers (.e. ShiftButton etc).\n" - "@return True to terminate dispatcher\n" - ) + - callback ("mouse_button_pressed_event", &gsi::PluginBase::mouse_press_event_noref, &gsi::PluginBase::f_mouse_press_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), - "@brief Handles the mouse button pressed event\n" - "This method will called by the view when a button is pressed on the mouse.\n" - "\n" - "First, the plugins that grabbed the mouse with \\grab_mouse will receive this event with 'prio' set to true " - "in the reverse order the plugins grabbed the mouse. The loop will terminate if one of the mouse event handlers " - "returns true.\n" - "\n" - "If that is not the case or no plugin has grabbed the mouse, the active plugin receives the mouse event with 'prio' set to true.\n" - "\n" - "If no receiver accepted the mouse event by returning true, it is sent again to all plugins with 'prio' set to false.\n" - "Again, the loop terminates if one of the receivers returns true. The second pass gives inactive plugins a chance to monitor the mouse " - "and implement specific actions - i.e. displaying the current position.\n" - "\n" - "This event is not sent immediately when the mouse button is pressed but when a signification movement for the mouse cursor away from the " - "original position is detected. If the mouse button is released before that, a mouse_clicked_event is sent rather than a press-move-release " - "sequence." - "\n" - "@param p The point at which the button was pressed\n" - "@param buttons A combination of the constants in the \\ButtonState class which codes both the mouse buttons and the key modifiers (.e. LeftButton, ShiftButton etc).\n" - "@return True to terminate dispatcher\n" - ) + - callback ("mouse_click_event", &gsi::PluginBase::mouse_click_event_noref, &gsi::PluginBase::f_mouse_click_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), - "@brief Handles the mouse button click event (after the button has been released)\n" - "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse button has been released without moving it.\n" - ) + - callback ("mouse_double_click_event", &gsi::PluginBase::mouse_double_click_event_noref, &gsi::PluginBase::f_mouse_double_click_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), - "@brief Handles the mouse button double-click event\n" - "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse button has been double-clicked.\n" - ) + - callback ("leave_event", &gsi::PluginBase::leave_event, &gsi::PluginBase::f_leave_event, gsi::arg ("prio"), - "@brief Handles the leave event (mouse leaves canvas area of view)\n" - "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse leaves the canvas area.\n" - "This method does not have a position nor button flags.\n" - ) + - callback ("enter_event", &gsi::PluginBase::enter_event, &gsi::PluginBase::f_enter_event, gsi::arg ("prio"), - "@brief Handles the enter event (mouse enters canvas area of view)\n" - "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse enters the canvas area.\n" - "This method does not have a position nor button flags.\n" - ) + - callback ("mouse_moved_event", &gsi::PluginBase::mouse_move_event_noref, &gsi::PluginBase::f_mouse_move_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), - "@brief Handles the mouse move event\n" - "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse is moved in the canvas area.\n" - ) + - callback ("mouse_button_released_event", &gsi::PluginBase::mouse_release_event_noref, &gsi::PluginBase::f_mouse_release_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), - "@brief Handles the mouse button release event\n" - "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse button is released.\n" - ) + - callback ("wheel_event", &gsi::PluginBase::wheel_event_noref, &gsi::PluginBase::f_wheel_event, gsi::arg ("delta"), gsi::arg ("horizontal"), gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), - "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse wheel is rotated.\n" - "Additional parameters for this event are 'delta' (the rotation angle in units of 1/8th degree) and 'horizontal' which is true when the horizontal wheel was rotated and " - "false if the vertical wheel was rotated.\n" - ) + - callback ("activated", &gsi::PluginBase::activated, &gsi::PluginBase::f_activated, - "@brief Gets called when the plugin is activated (selected in the tool bar)\n" - ) + - callback ("deactivated", &gsi::PluginBase::deactivated, &gsi::PluginBase::f_deactivated, - "@brief Gets called when the plugin is deactivated and another plugin is activated\n" - ) + - callback ("drag_cancel", &gsi::PluginBase::drag_cancel, &gsi::PluginBase::f_drag_cancel, - "@brief Gets called on various occasions when a drag operation should be canceled\n" - "If the plugin implements some press-and-drag or a click-and-drag operation, this callback should " - "cancel this operation and return in some state waiting for a new mouse event." - ) + - callback ("update", &gsi::PluginBase::update, &gsi::PluginBase::f_update, - "@brief Gets called when the view has changed\n" - "This method is called in particular if the view has changed the visible rectangle, i.e. after zooming in or out or panning. " - "This callback can be used to update any internal states that depend on the view's state." - ) + - method ("grab_mouse", &gsi::PluginBase::grab_mouse, - "@brief Redirects mouse events to this plugin, even if the plugin is not active.\n" - ) + - method ("ungrab_mouse", &gsi::PluginBase::ungrab_mouse, - "@brief Removes a mouse grab registered with \\grab_mouse.\n" - ) + - method ("set_cursor", &gsi::PluginBase::set_cursor, gsi::arg ("cursor_type"), - "@brief Sets the cursor in the view area to the given type\n" - "Setting the cursor has an effect only inside event handlers, i.e. mouse_press_event. The cursor is not set permanently. Is is reset " - "in the mouse move handler unless a button is pressed or the cursor is explicitly set again in the mouse_move_event.\n" - "\n" - "The cursor type is one of the cursor constants in the \\Cursor class, i.e. 'CursorArrow' for the normal cursor." - ) + - callback ("has_tracking_position", &gsi::PluginBase::has_tracking_position, &gsi::PluginBase::f_has_tracking_position, - "@brief Gets a value indicating whether the plugin provides a tracking position\n" - "The tracking position is shown in the lower-left corner of the layout window to indicate the current position.\n" - "If this method returns true for the active service, the application will fetch the position by calling \\tracking_position " - "rather than displaying the original mouse position.\n" - "\n" - "This method has been added in version 0.27.6." - ) + - callback ("tracking_position", &gsi::PluginBase::tracking_position, &gsi::PluginBase::f_tracking_position, - "@brief Gets the tracking position\n" - "See \\has_tracking_position for details.\n" - "\n" - "This method has been added in version 0.27.6." - ), - "@brief The plugin object\n" - "\n" - "This class provides the actual plugin implementation. Each view gets its own instance of the plugin class. The plugin factory \\PluginFactory class " - "must be specialized to provide a factory for new objects of the Plugin class. See the documentation there for details about the plugin mechanism and " - "the basic concepts.\n" - "\n" - "This class has been introduced in version 0.22.\n" -); - -class CursorNamespace { }; - -static int cursor_shape_none () { return int (lay::Cursor::none); } -static int cursor_shape_arrow () { return int (lay::Cursor::arrow); } -static int cursor_shape_up_arrow () { return int (lay::Cursor::up_arrow); } -static int cursor_shape_cross () { return int (lay::Cursor::cross); } -static int cursor_shape_wait () { return int (lay::Cursor::wait); } -static int cursor_shape_i_beam () { return int (lay::Cursor::i_beam); } -static int cursor_shape_size_ver () { return int (lay::Cursor::size_ver); } -static int cursor_shape_size_hor () { return int (lay::Cursor::size_hor); } -static int cursor_shape_size_bdiag () { return int (lay::Cursor::size_bdiag); } -static int cursor_shape_size_fdiag () { return int (lay::Cursor::size_fdiag); } -static int cursor_shape_size_all () { return int (lay::Cursor::size_all); } -static int cursor_shape_blank () { return int (lay::Cursor::blank); } -static int cursor_shape_split_v () { return int (lay::Cursor::split_v); } -static int cursor_shape_split_h () { return int (lay::Cursor::split_h); } -static int cursor_shape_pointing_hand () { return int (lay::Cursor::pointing_hand); } -static int cursor_shape_forbidden () { return int (lay::Cursor::forbidden); } -static int cursor_shape_whats_this () { return int (lay::Cursor::whats_this); } -static int cursor_shape_busy () { return int (lay::Cursor::busy); } -static int cursor_shape_open_hand () { return int (lay::Cursor::open_hand); } -static int cursor_shape_closed_hand () { return int (lay::Cursor::closed_hand); } - -Class decl_Cursor ("lay", "Cursor", - method ("None", &cursor_shape_none, "@brief 'No cursor (default)' constant for \\set_cursor (resets cursor to default)") + - method ("Arrow", &cursor_shape_arrow, "@brief 'Arrow cursor' constant") + - method ("UpArrow", &cursor_shape_up_arrow, "@brief 'Upward arrow cursor' constant") + - method ("Cross", &cursor_shape_cross, "@brief 'Cross cursor' constant") + - method ("Wait", &cursor_shape_wait, "@brief 'Waiting cursor' constant") + - method ("IBeam", &cursor_shape_i_beam, "@brief 'I beam (text insert) cursor' constant") + - method ("SizeVer", &cursor_shape_size_ver, "@brief 'Vertical resize cursor' constant") + - method ("SizeHor", &cursor_shape_size_hor, "@brief 'Horizontal resize cursor' constant") + - method ("SizeBDiag", &cursor_shape_size_bdiag, "@brief 'Backward diagonal resize cursor' constant") + - method ("SizeFDiag", &cursor_shape_size_fdiag, "@brief 'Forward diagonal resize cursor' constant") + - method ("SizeAll", &cursor_shape_size_all, "@brief 'Size all directions cursor' constant") + - method ("Blank", &cursor_shape_blank, "@brief 'Blank cursor' constant") + - method ("SplitV", &cursor_shape_split_v, "@brief 'Split vertical cursor' constant") + - method ("SplitH", &cursor_shape_split_h, "@brief 'split_horizontal cursor' constant") + - method ("PointingHand", &cursor_shape_pointing_hand, "@brief 'Pointing hand cursor' constant") + - method ("Forbidden", &cursor_shape_forbidden, "@brief 'Forbidden area cursor' constant") + - method ("WhatsThis", &cursor_shape_whats_this, "@brief 'Question mark cursor' constant") + - method ("Busy", &cursor_shape_busy, "@brief 'Busy state cursor' constant") + - method ("OpenHand", &cursor_shape_open_hand, "@brief 'Open hand cursor' constant") + - method ("ClosedHand", &cursor_shape_closed_hand, "@brief 'Closed hand cursor' constant"), - "@brief The namespace for the cursor constants\n" - "This class defines the constants for the cursor setting (for example for class \\Plugin, method set_cursor)." - "\n" - "This class has been introduced in version 0.22.\n" -); - -class ButtonStateNamespace { }; - -static int const_ShiftButton() { return (int) lay::ShiftButton; } -static int const_ControlButton() { return (int) lay::ControlButton; } -static int const_AltButton() { return (int) lay::AltButton; } -static int const_LeftButton() { return (int) lay::LeftButton; } -static int const_MidButton() { return (int) lay::MidButton; } -static int const_RightButton() { return (int) lay::RightButton; } - -Class decl_ButtonState ("lay", "ButtonState", - method ("ShiftKey", &const_ShiftButton, "@brief Indicates that the Shift key is pressed\nThis constant is combined with other constants within \\ButtonState") + - method ("ControlKey", &const_ControlButton, "@brief Indicates that the Control key is pressed\nThis constant is combined with other constants within \\ButtonState") + - method ("AltKey", &const_AltButton, "@brief Indicates that the Alt key is pressed\nThis constant is combined with other constants within \\ButtonState") + - method ("LeftButton", &const_LeftButton, "@brief Indicates that the left mouse button is pressed\nThis constant is combined with other constants within \\ButtonState") + - method ("MidButton", &const_MidButton, "@brief Indicates that the middle mouse button is pressed\nThis constant is combined with other constants within \\ButtonState") + - method ("RightButton", &const_RightButton, "@brief Indicates that the right mouse button is pressed\nThis constant is combined with other constants within \\ButtonState"), - "@brief The namespace for the button state flags in the mouse events of the Plugin class.\n" - "This class defines the constants for the button state. In the event handler, the button state is " - "indicated by a bitwise combination of these constants. See \\Plugin for further details." - "\n" - "This class has been introduced in version 0.22.\n" -); - -class KeyCodesNamespace { }; - -static int const_KeyEscape() { return (int) lay::KeyEscape; } -static int const_KeyTab() { return (int) lay::KeyTab; } -static int const_KeyBacktab() { return (int) lay::KeyBacktab; } -static int const_KeyBackspace() { return (int) lay::KeyBackspace; } -static int const_KeyReturn() { return (int) lay::KeyReturn; } -static int const_KeyEnter() { return (int) lay::KeyEnter; } -static int const_KeyInsert() { return (int) lay::KeyInsert; } -static int const_KeyDelete() { return (int) lay::KeyDelete; } -static int const_KeyHome() { return (int) lay::KeyHome; } -static int const_KeyEnd() { return (int) lay::KeyEnd; } -static int const_KeyDown() { return (int) lay::KeyDown; } -static int const_KeyUp() { return (int) lay::KeyUp; } -static int const_KeyLeft() { return (int) lay::KeyLeft; } -static int const_KeyRight() { return (int) lay::KeyRight; } -static int const_KeyPageUp() { return (int) lay::KeyPageUp; } -static int const_KeyPageDown() { return (int) lay::KeyPageDown; } - -Class decl_KeyCode ("lay", "KeyCode", - method ("Escape", &const_KeyEscape, "@brief Indicates the Escape key") + - method ("Tab", &const_KeyTab, "@brief Indicates the Tab key") + - method ("Backtab", &const_KeyBacktab, "@brief Indicates the Backtab key") + - method ("Backspace", &const_KeyBackspace, "@brief Indicates the Backspace key") + - method ("Return", &const_KeyReturn, "@brief Indicates the Return key") + - method ("Enter", &const_KeyEnter, "@brief Indicates the Enter key") + - method ("Insert", &const_KeyInsert, "@brief Indicates the Insert key") + - method ("Delete", &const_KeyDelete, "@brief Indicates the Delete key") + - method ("Home", &const_KeyHome, "@brief Indicates the Home key") + - method ("End", &const_KeyEnd, "@brief Indicates the End key") + - method ("Down", &const_KeyDown, "@brief Indicates the Down key") + - method ("Up", &const_KeyUp, "@brief Indicates the Up key") + - method ("Left", &const_KeyLeft, "@brief Indicates the Left key") + - method ("Right", &const_KeyRight, "@brief Indicates the Right key") + - method ("PageUp", &const_KeyPageUp, "@brief Indicates the PageUp key") + - method ("PageDown", &const_KeyPageDown, "@brief Indicates the PageDown key"), - "@brief The namespace for the some key codes.\n" - "This namespace defines some key codes understood by built-in \\LayoutView components. " - "When compiling with Qt, these codes are compatible with Qt's key codes.\n" - "The key codes are intended to be used when directly interfacing with \\LayoutView in non-Qt-based environments.\n" - "\n" - "This class has been introduced in version 0.28.\n" -); - -static std::vector -get_config_names (lay::Dispatcher *dispatcher) -{ - std::vector names; - dispatcher->get_config_names (names); - return names; -} - -static lay::Dispatcher *dispatcher_instance () -{ - return lay::Dispatcher::instance (); -} - -static tl::Variant get_config (lay::Dispatcher *dispatcher, const std::string &name) -{ - std::string value; - if (dispatcher->config_get (name, value)) { - return tl::Variant (value); - } else { - return tl::Variant (); - } -} - -/** - * @brief Exposes the Dispatcher interface - * - * This interface is intentionally not derived from Plugin. It is used currently to - * identify the dispatcher node for configuration. The Plugin nature of this interface - * is somewhat artificial and may be removed later. - * - * TODO: this is a duplicate of the respective methods in LayoutView and Application. - * This is intentional since we don't want to spend the only derivation path on this. - * Once there is a mixin concept, provide a path through that concept. - */ -Class decl_Dispatcher ("lay", "Dispatcher", - method ("clear_config", &lay::Dispatcher::clear_config, - "@brief Clears the configuration parameters\n" - ) + - method ("instance", &dispatcher_instance, - "@brief Gets the singleton instance of the Dispatcher object\n" - "\n" - "@return The instance\n" - ) + - method ("write_config", &lay::Dispatcher::write_config, gsi::arg ("file_name"), - "@brief Writes configuration to a file\n" - "@return A value indicating whether the operation was successful\n" - "\n" - "If the configuration file cannot be written, false \n" - "is returned but no exception is thrown.\n" - ) + - method ("read_config", &lay::Dispatcher::read_config, gsi::arg ("file_name"), - "@brief Reads the configuration from a file\n" - "@return A value indicating whether the operation was successful\n" - "\n" - "This method silently does nothing, if the config file does not\n" - "exist. If it does and an error occurred, the error message is printed\n" - "on stderr. In both cases, false is returned.\n" - ) + - method_ext ("get_config", &get_config, gsi::arg ("name"), - "@brief Gets the value of a local configuration parameter\n" - "\n" - "@param name The name of the configuration parameter whose value shall be obtained (a string)\n" - "\n" - "@return The value of the parameter or nil if there is no such parameter\n" - ) + - method ("set_config", (void (lay::Dispatcher::*) (const std::string &, const std::string &)) &lay::Dispatcher::config_set, gsi::arg ("name"), gsi::arg ("value"), - "@brief Set a local configuration parameter with the given name to the given value\n" - "\n" - "@param name The name of the configuration parameter to set\n" - "@param value The value to which to set the configuration parameter\n" - "\n" - "This method sets a configuration parameter with the given name to the given value. " - "Values can only be strings. Numerical values have to be converted into strings first. " - "Local configuration parameters override global configurations for this specific view. " - "This allows for example to override global settings of background colors. " - "Any local settings are not written to the configuration file. " - ) + - method_ext ("get_config_names", &get_config_names, - "@brief Gets the configuration parameter names\n" - "\n" - "@return A list of configuration parameter names\n" - "\n" - "This method returns the names of all known configuration parameters. These names can be used to " - "get and set configuration parameter values.\n" - ) + - method ("commit_config", &lay::Dispatcher::config_end, - "@brief Commits the configuration settings\n" - "\n" - "Some configuration options are queued for performance reasons and become active only after 'commit_config' has been called. " - "After a sequence of \\set_config calls, this method should be called to activate the " - "settings made by these calls.\n" - ), - "@brief Root of the configuration space in the plugin context and menu dispatcher\n" - "\n" - "This class provides access to the root configuration space in the context " - "of plugin programming. You can use this class to obtain configuration parameters " - "from the configuration tree during plugin initialization. However, the " - "preferred way of plugin configuration is through \\Plugin#configure.\n" - "\n" - "Currently, the application object provides an identical entry point for configuration modification. " - "For example, \"Application::instance.set_config\" is identical to \"Dispatcher::instance.set_config\". " - "Hence there is little motivation for the Dispatcher class currently and " - "this interface may be modified or removed in the future." - "\n" - "This class has been introduced in version 0.25 as 'PluginRoot'.\n" - "It is renamed and enhanced as 'Dispatcher' in 0.27." -); - -} diff --git a/src/laybasic/laybasic/layEditable.cc b/src/laybasic/laybasic/layEditable.cc index 0c96df3c3..fefbe3d26 100644 --- a/src/laybasic/laybasic/layEditable.cc +++ b/src/laybasic/laybasic/layEditable.cc @@ -54,6 +54,15 @@ Editable::Editable (lay::Editables *editables) } } +void +Editable::init (lay::Editables *editables) +{ + mp_editables = editables; + if (editables) { + editables->m_editables.push_back (this); + } +} + Editable::~Editable () { // Reasoning for reset (): on MSVC, virtual functions must not be called inside diff --git a/src/laybasic/laybasic/layEditable.h b/src/laybasic/laybasic/layEditable.h index 4d1ba5da5..cb696600c 100644 --- a/src/laybasic/laybasic/layEditable.h +++ b/src/laybasic/laybasic/layEditable.h @@ -71,6 +71,11 @@ class LAYBASIC_PUBLIC Editable */ Editable (Editables *editables = 0); + /** + * @brief Initializes after constructor with a null pointer was called + */ + void init (Editables *editables); + /** * @brief The constructor */ diff --git a/src/layui/layui/layEditorOptionsPage.cc b/src/laybasic/laybasic/layEditorOptionsPage.cc similarity index 51% rename from src/layui/layui/layEditorOptionsPage.cc rename to src/laybasic/laybasic/layEditorOptionsPage.cc index 2da105f86..0efde1109 100644 --- a/src/layui/layui/layEditorOptionsPage.cc +++ b/src/laybasic/laybasic/layEditorOptionsPage.cc @@ -26,6 +26,10 @@ #include "layEditorOptionsPage.h" #include "layEditorOptionsPages.h" #include "layLayoutViewBase.h" +#include "tlExceptions.h" + +#include +#include namespace lay { @@ -34,16 +38,96 @@ namespace lay // EditorOptionsPage implementation EditorOptionsPage::EditorOptionsPage (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) - : QWidget (0), mp_owner (0), m_active (true), mp_plugin_declaration (0), mp_dispatcher (dispatcher), mp_view (view) + : QWidget (0), mp_owner (0), m_active (true), m_focus_page (false), m_modal_page (false), mp_plugin_declaration (0), mp_dispatcher (dispatcher), mp_view (view) { attach_events (); } +EditorOptionsPage::EditorOptionsPage () + : QWidget (0), mp_owner (0), m_active (true), m_focus_page (false), m_modal_page (false), mp_plugin_declaration (0), mp_dispatcher (0), mp_view (0) +{ + // .. nothing yet .. +} + EditorOptionsPage::~EditorOptionsPage () { set_owner (0); } +void +EditorOptionsPage::init (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) +{ + mp_view = view; + mp_dispatcher = dispatcher; + attach_events (); +} + +void +EditorOptionsPage::edited () +{ + apply (dispatcher ()); +} + +static bool is_parent_widget (QWidget *w, QWidget *parent) +{ + while (w && w != parent) { + w = dynamic_cast (w->parent ()); + } + return w == parent; +} + +bool +EditorOptionsPage::focusNextPrevChild (bool next) +{ + bool res = QWidget::focusNextPrevChild (next); + + // Stop making the focus leave the page - this way we can jump back to the + // view on "enter" + if (res && ! is_modal_page () && ! is_parent_widget (QApplication::focusWidget (), this) && focusWidget ()) { + focusWidget ()->setFocus (); + } + + return res; +} + +void +EditorOptionsPage::keyPressEvent (QKeyEvent *event) +{ +BEGIN_PROTECTED + if (! is_modal_page () && event->modifiers () == Qt::NoModifier && event->key () == Qt::Key_Return) { + // The Return key on a non-modal page commits the values and gives back the focus + // to the view + apply (dispatcher ()); + view ()->set_focus (); + event->accept (); + } else { + QWidget::keyPressEvent (event); + } +END_PROTECTED +} + +void +EditorOptionsPage::set_focus () +{ + setFocus (Qt::TabFocusReason); + QWidget::focusNextPrevChild (true); +} + +int +EditorOptionsPage::show () +{ + if (mp_owner && m_active) { + if (! is_modal_page ()) { + mp_owner->make_page_current (this); + return -1; + } else { + return mp_owner->exec_modal (this) ? 1 : 0; + } + } else { + return -1; + } +} + void EditorOptionsPage::attach_events () { diff --git a/src/layui/layui/layEditorOptionsPage.h b/src/laybasic/laybasic/layEditorOptionsPage.h similarity index 78% rename from src/layui/layui/layEditorOptionsPage.h rename to src/laybasic/laybasic/layEditorOptionsPage.h index 418892287..33b9fc1d3 100644 --- a/src/layui/layui/layEditorOptionsPage.h +++ b/src/laybasic/laybasic/layEditorOptionsPage.h @@ -25,7 +25,7 @@ #ifndef HDR_layEditorOptionsPage #define HDR_layEditorOptionsPage -#include "layuiCommon.h" +#include "laybasicCommon.h" #include "tlObject.h" @@ -49,13 +49,14 @@ class EditorOptionsPages; /** * @brief The base class for a object properties page */ -class LAYUI_PUBLIC EditorOptionsPage +class LAYBASIC_PUBLIC EditorOptionsPage : public QWidget, public tl::Object { Q_OBJECT public: EditorOptionsPage (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher); + EditorOptionsPage (); virtual ~EditorOptionsPage (); virtual std::string title () const = 0; @@ -65,20 +66,28 @@ Q_OBJECT virtual void commit_recent (lay::Dispatcher * /*root*/) { } virtual void config_recent_for_layer (lay::Dispatcher * /*root*/, const db::LayerProperties & /*lp*/, int /*cv_index*/) { } + bool is_focus_page () const { return m_focus_page; } + void set_focus_page (bool f) { m_focus_page = f; } + void set_focus (); + + bool is_modal_page () const { return m_modal_page; } + void set_modal_page (bool f) { m_modal_page = f; } + bool active () const { return m_active; } void activate (bool active); void set_owner (EditorOptionsPages *owner); + /** + * @brief Shows the editor page + * @return -1, if the page is shown non-modal, otherwise 1 or 0 if the dialog was accepted (1) or rejected (0) + */ + int show (); + const lay::PluginDeclaration *plugin_declaration () const { return mp_plugin_declaration; } void set_plugin_declaration (const lay::PluginDeclaration *pd) { mp_plugin_declaration = pd; } -protected slots: - void edited () - { - apply (dispatcher ()); - } + void init (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher); -protected: lay::Dispatcher *dispatcher () const { return mp_dispatcher; @@ -89,12 +98,21 @@ protected slots: return mp_view; } +protected slots: + void edited (); + +protected: virtual void active_cellview_changed () { } virtual void technology_changed (const std::string & /*tech*/) { } + virtual bool focusNextPrevChild (bool next); + virtual void keyPressEvent (QKeyEvent *event); + private: EditorOptionsPages *mp_owner; bool m_active; + bool m_focus_page; + bool m_modal_page; const lay::PluginDeclaration *mp_plugin_declaration; lay::Dispatcher *mp_dispatcher; lay::LayoutViewBase *mp_view; diff --git a/src/laybasic/laybasic/layEditorOptionsPages.cc b/src/laybasic/laybasic/layEditorOptionsPages.cc new file mode 100644 index 000000000..994369917 --- /dev/null +++ b/src/laybasic/laybasic/layEditorOptionsPages.cc @@ -0,0 +1,449 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 + +*/ + +#if defined(HAVE_QT) + +#include "tlInternational.h" +#include "layEditorOptionsPages.h" +#include "tlExceptions.h" +#include "layPlugin.h" +#include "layLayoutViewBase.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace lay +{ + +// ------------------------------------------------------------------ +// EditorOptionsPages implementation + +struct EOPCompareOp +{ + bool operator() (lay::EditorOptionsPage *a, lay::EditorOptionsPage *b) const + { + return a->order () < b->order (); + } +}; + +EditorOptionsPages::EditorOptionsPages (QWidget *parent, const std::vector &pages, lay::Dispatcher *dispatcher) + : QFrame (parent), mp_dispatcher (dispatcher) +{ + mp_modal_pages = new EditorOptionsModalPages (this); + + QVBoxLayout *ly1 = new QVBoxLayout (this); + ly1->setContentsMargins (0, 0, 0, 0); + + mp_pages = new QTabWidget (this); + mp_pages->setSizePolicy (QSizePolicy (QSizePolicy::Ignored, QSizePolicy::Ignored)); + ly1->addWidget (mp_pages); + + m_pages = pages; + for (std::vector ::const_iterator p = m_pages.begin (); p != m_pages.end (); ++p) { + (*p)->set_owner (this); + } + + update (0); + setup (); +} + +EditorOptionsPages::~EditorOptionsPages () +{ + while (m_pages.size () > 0) { + delete m_pages.front (); + } + + delete mp_modal_pages; + mp_modal_pages = 0; +} + +void +EditorOptionsPages::focusInEvent (QFocusEvent * /*event*/) +{ + // Sends the focus to the current page's last focus owner + if (mp_pages->currentWidget () && mp_pages->currentWidget ()->focusWidget ()) { + mp_pages->currentWidget ()->focusWidget ()->setFocus (); + } +} + +bool +EditorOptionsPages::has_content () const +{ + for (std::vector ::const_iterator p = m_pages.begin (); p != m_pages.end (); ++p) { + if ((*p)->active () && ! (*p)->is_modal_page ()) { + return true; + } + } + return false; +} + +bool +EditorOptionsPages::has_modal_content () const +{ + for (std::vector ::const_iterator p = m_pages.begin (); p != m_pages.end (); ++p) { + if ((*p)->active () && (*p)->is_modal_page ()) { + return true; + } + } + return false; +} + +bool +EditorOptionsPages::exec_modal (EditorOptionsPage *page) +{ + for (int i = 0; i < mp_modal_pages->count (); ++i) { + + if (mp_modal_pages->widget (i) == page) { + + // found the page - make it current and show the dialog + mp_modal_pages->set_current_index (i); + page->setup (mp_dispatcher); + page->set_focus (); + return mp_modal_pages->exec () != 0; + + } + + } + + return false; +} + +void +EditorOptionsPages::activate (const lay::Plugin *plugin) +{ + for (auto op = m_pages.begin (); op != m_pages.end (); ++op) { + bool is_active = false; + if ((*op)->plugin_declaration () == 0) { + is_active = (plugin && plugin->plugin_declaration ()->enable_catchall_editor_options_pages ()); + } else if (plugin && plugin->plugin_declaration () == (*op)->plugin_declaration ()) { + is_active = true; + } + (*op)->activate (is_active); + } +} + +void +EditorOptionsPages::unregister_page (lay::EditorOptionsPage *page) +{ + std::vector pages; + for (std::vector ::const_iterator p = m_pages.begin (); p != m_pages.end (); ++p) { + if (*p != page) { + pages.push_back (*p); + } + } + m_pages = pages; + update (0); +} + +void +EditorOptionsPages::make_page_current (lay::EditorOptionsPage *page) +{ + for (int i = 0; i < mp_pages->count (); ++i) { + if (mp_pages->widget (i) == page) { + mp_pages->setCurrentIndex (i); + page->setup (mp_dispatcher); + page->set_focus (); + break; + } + } +} + +void +EditorOptionsPages::activate_page (lay::EditorOptionsPage *page) +{ + try { + if (page->active ()) { + page->setup (mp_dispatcher); + } + } catch (...) { + // catch any errors related to configuration file errors etc. + } + + update (page); +} + +void +EditorOptionsPages::update (lay::EditorOptionsPage *page) +{ + std::vector sorted_pages = m_pages; + std::sort (sorted_pages.begin (), sorted_pages.end (), EOPCompareOp ()); + + if (! page && m_pages.size () > 0) { + page = m_pages.back (); + } + + while (mp_pages->count () > 0) { + mp_pages->removeTab (0); + } + + while (mp_modal_pages->count () > 0) { + mp_modal_pages->remove_page (0); + } + + int index = -1; + int modal_index = -1; + + for (std::vector ::iterator p = sorted_pages.begin (); p != sorted_pages.end (); ++p) { + if ((*p)->active ()) { + if (! (*p)->is_modal_page ()) { + if ((*p) == page) { + index = mp_pages->count (); + } + mp_pages->addTab (*p, tl::to_qstring ((*p)->title ())); + } else { + if ((*p) == page) { + modal_index = mp_modal_pages->count (); + } + mp_modal_pages->add_page (*p); + } + } else { + (*p)->setParent (0); + } + } + + if (index < 0) { + index = mp_pages->currentIndex (); + } + if (index >= int (mp_pages->count ())) { + index = mp_pages->count () - 1; + } + mp_pages->setCurrentIndex (index); + + if (modal_index < 0) { + modal_index = mp_modal_pages->current_index (); + } + if (modal_index >= int (mp_modal_pages->count ())) { + modal_index = mp_modal_pages->count () - 1; + } + mp_modal_pages->set_current_index (modal_index); + + setVisible (mp_pages->count () > 0); +} + +void +EditorOptionsPages::setup () +{ +BEGIN_PROTECTED + + for (std::vector ::iterator p = m_pages.begin (); p != m_pages.end (); ++p) { + if ((*p)->active ()) { + (*p)->setup (mp_dispatcher); + } + } + + // make the display consistent with the status (this is important for + // PCell parameters where the PCell may be asked to modify the parameters) + do_apply (false); + do_apply (true); + +END_PROTECTED_W (this) +} + +void +EditorOptionsPages::do_apply (bool modal) +{ + for (std::vector ::iterator p = m_pages.begin (); p != m_pages.end (); ++p) { + if ((*p)->active () && modal == (*p)->is_modal_page ()) { + // NOTE: we apply to the root dispatcher, so other dispatchers (views) get informed too. + (*p)->apply (mp_dispatcher->dispatcher ()); + } + } +} + +void +EditorOptionsPages::apply () +{ +BEGIN_PROTECTED + do_apply (false); +END_PROTECTED_W (this) +} + +// ------------------------------------------------------------------ +// EditorOptionsModalPages implementation + +EditorOptionsModalPages::EditorOptionsModalPages (EditorOptionsPages *parent) + : QDialog (parent), mp_parent (parent), mp_single_page (0) +{ + QVBoxLayout *ly = new QVBoxLayout (this); + ly->setContentsMargins (0, 0, 0, 0); + + QVBoxLayout *ly4 = new QVBoxLayout (0); + ly4->setContentsMargins (6, 6, 6, 0); + ly->addLayout (ly4); + mp_pages = new QTabWidget (this); + ly4->addWidget (mp_pages, 1); +#if QT_VERSION >= 0x50400 + mp_pages->setTabBarAutoHide (true); +#endif + mp_pages->hide (); + + mp_single_page_frame = new QFrame (this); + QVBoxLayout *ly2 = new QVBoxLayout (mp_single_page_frame); + ly2->setContentsMargins (0, 0, 0, 0); + ly->addWidget (mp_single_page_frame, 1); + mp_single_page_frame->hide (); + + QVBoxLayout *ly3 = new QVBoxLayout (0); + ly3->setContentsMargins (6, 6, 6, 6); + ly->addLayout (ly3); + mp_button_box = new QDialogButtonBox (this); + ly3->addWidget (mp_button_box); + mp_button_box->setOrientation (Qt::Horizontal); + mp_button_box->setStandardButtons (QDialogButtonBox::Cancel | QDialogButtonBox::Apply | QDialogButtonBox::Ok); + + connect (mp_button_box, SIGNAL (clicked(QAbstractButton *)), this, SLOT (clicked(QAbstractButton *))); + connect (mp_button_box, SIGNAL (accepted()), this, SLOT (accept())); + connect (mp_button_box, SIGNAL (rejected()), this, SLOT (reject())); + + update_title (); +} + +EditorOptionsModalPages::~EditorOptionsModalPages () +{ + // .. nothing yet .. +} + +int +EditorOptionsModalPages::count () +{ + return mp_single_page ? 1 : mp_pages->count (); +} + +int +EditorOptionsModalPages::current_index () +{ + return mp_single_page ? 0 : mp_pages->currentIndex (); +} + +void +EditorOptionsModalPages::set_current_index (int index) +{ + if (! mp_single_page) { + mp_pages->setCurrentIndex (index); + } +} + +void +EditorOptionsModalPages::add_page (EditorOptionsPage *page) +{ + if (! mp_single_page) { + if (mp_pages->count () == 0) { + mp_single_page = page; + mp_single_page->setParent (mp_single_page_frame); + mp_single_page_frame->layout ()->addWidget (mp_single_page); + mp_single_page_frame->show (); + mp_pages->hide (); + } else { + mp_pages->addTab (page, tl::to_qstring (page->title ())); + } + } else { + mp_pages->clear (); + mp_single_page_frame->layout ()->removeWidget (mp_single_page); + mp_single_page_frame->hide (); + mp_pages->addTab (mp_single_page, tl::to_qstring (mp_single_page->title ())); + mp_single_page = 0; + mp_pages->addTab (page, tl::to_qstring (page->title ())); + mp_pages->show (); + } + + update_title (); +} + +void +EditorOptionsModalPages::remove_page (int index) +{ + if (mp_single_page) { + if (index == 0) { + mp_single_page->setParent (0); + mp_single_page = 0; + mp_single_page_frame->hide (); + mp_single_page_frame->layout ()->removeWidget (mp_single_page); + } + } else { + mp_pages->removeTab (index); + if (mp_pages->count () == 1) { + mp_pages->hide (); + mp_single_page = dynamic_cast (mp_pages->widget (0)); + mp_pages->removeTab (0); + mp_single_page->setParent (mp_single_page_frame); + mp_single_page_frame->layout ()->addWidget (mp_single_page); + mp_single_page_frame->show (); + } + } + + update_title (); +} + +void +EditorOptionsModalPages::update_title () +{ + if (mp_single_page) { + setWindowTitle (tl::to_qstring (mp_single_page->title ())); + } else { + setWindowTitle (tr ("Editor Options")); + } +} + +EditorOptionsPage * +EditorOptionsModalPages::widget (int index) +{ + if (mp_single_page) { + return index == 0 ? mp_single_page : 0; + } else { + return dynamic_cast (mp_pages->widget (index)); + } +} + +void +EditorOptionsModalPages::accept () +{ +BEGIN_PROTECTED + mp_parent->do_apply (true); + QDialog::accept (); +END_PROTECTED +} + +void +EditorOptionsModalPages::reject () +{ + QDialog::reject (); +} + +void +EditorOptionsModalPages::clicked (QAbstractButton *button) +{ +BEGIN_PROTECTED + if (button == mp_button_box->button (QDialogButtonBox::Apply)) { + mp_parent->do_apply (true); + } +END_PROTECTED +} + +} + +#endif diff --git a/src/layui/layui/layEditorOptionsPages.h b/src/laybasic/laybasic/layEditorOptionsPages.h similarity index 60% rename from src/layui/layui/layEditorOptionsPages.h rename to src/laybasic/laybasic/layEditorOptionsPages.h index 07c172e6b..7ce48559c 100644 --- a/src/layui/layui/layEditorOptionsPages.h +++ b/src/laybasic/laybasic/layEditorOptionsPages.h @@ -25,17 +25,19 @@ #ifndef HDR_layEditorOptionsPages #define HDR_layEditorOptionsPages -#include "layuiCommon.h" +#include "laybasicCommon.h" #include "layEditorOptionsPage.h" -#include - #include +#include + #include #include class QTabWidget; class QLabel; +class QDialogButtonBox; +class QAbstractButton; namespace lay { @@ -43,11 +45,12 @@ namespace lay class PluginDeclaration; class Dispatcher; class Plugin; +class EditorOptionsModalPages; /** - * @brief The object properties dialog + * @brief The object properties tab widget */ -class LAYUI_PUBLIC EditorOptionsPages +class LAYBASIC_PUBLIC EditorOptionsPages : public QFrame { Q_OBJECT @@ -58,7 +61,10 @@ Q_OBJECT void unregister_page (lay::EditorOptionsPage *page); void activate_page (lay::EditorOptionsPage *page); + void activate (const lay::Plugin *plugin); void focusInEvent (QFocusEvent *event); + void make_page_current (lay::EditorOptionsPage *page); + bool exec_modal (lay::EditorOptionsPage *page); const std::vector &pages () const { @@ -66,6 +72,8 @@ Q_OBJECT } bool has_content () const; + bool has_modal_content () const; + void do_apply (bool modal); public slots: void apply (); @@ -75,9 +83,43 @@ public slots: std::vector m_pages; lay::Dispatcher *mp_dispatcher; QTabWidget *mp_pages; + EditorOptionsModalPages *mp_modal_pages; void update (lay::EditorOptionsPage *page); - void do_apply (); +}; + +/** + * @brief The object properties modal page dialog + */ +class LAYBASIC_PUBLIC EditorOptionsModalPages + : public QDialog +{ +Q_OBJECT + +public: + EditorOptionsModalPages (EditorOptionsPages *parent); + ~EditorOptionsModalPages (); + + int count (); + int current_index (); + void set_current_index (int index); + void add_page (EditorOptionsPage *page); + void remove_page (int index); + EditorOptionsPage *widget (int index); + +private slots: + void accept (); + void reject (); + void clicked (QAbstractButton *button); + +private: + EditorOptionsPages *mp_parent; + QTabWidget *mp_pages; + QFrame *mp_single_page_frame; + EditorOptionsPage *mp_single_page; + QDialogButtonBox *mp_button_box; + + void update_title (); }; } diff --git a/src/laybasic/laybasic/layEditorServiceBase.cc b/src/laybasic/laybasic/layEditorServiceBase.cc index dfe6b27cb..9be73bf37 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.cc +++ b/src/laybasic/laybasic/layEditorServiceBase.cc @@ -21,6 +21,8 @@ */ #include "layEditorServiceBase.h" +#include "layEditorOptionsPage.h" +#include "layEditorOptionsPages.h" #include "layViewport.h" #include "layLayoutViewBase.h" #include "laybasicConfig.h" @@ -207,16 +209,26 @@ class EdgeMarkerViewObject // -------------------------------------------------------------------------------------- EditorServiceBase::EditorServiceBase (LayoutViewBase *view) - : lay::ViewService (view->canvas ()), + : lay::ViewService (view ? view->canvas () : 0), lay::Editable (view), lay::Plugin (view), mp_view (view), m_cursor_enabled (true), - m_has_tracking_position (false) + m_has_tracking_position (false), + m_active (false) { // .. nothing yet .. } +void +EditorServiceBase::init (LayoutViewBase *view) +{ + mp_view = view; + lay::Plugin::init (view); + lay::ViewService::init (view ? view->canvas () : 0); + lay::Editable::init (view); +} + EditorServiceBase::~EditorServiceBase () { clear_mouse_cursors (); @@ -265,9 +277,11 @@ EditorServiceBase::clear_mouse_cursors () } void -EditorServiceBase::mouse_cursor_from_snap_details (const lay::PointSnapToObjectResult &snap_details) +EditorServiceBase::mouse_cursor_from_snap_details (const lay::PointSnapToObjectResult &snap_details, bool noclear) { - clear_mouse_cursors (); + if (! noclear) { + clear_mouse_cursors (); + } add_mouse_cursor (snap_details.snapped_point, snap_details.object_snap == lay::PointSnapToObjectResult::ObjectVertex || @@ -319,15 +333,95 @@ void EditorServiceBase::deactivated () { clear_mouse_cursors (); + if (ui ()) { + ui ()->ungrab_mouse (this); + } + m_active = false; +} + +void +EditorServiceBase::activated () +{ + m_active = true; +} + +#if defined(HAVE_QT) + +std::vector +EditorServiceBase::editor_options_pages () +{ + lay::EditorOptionsPages *eo_pages = mp_view->editor_options_pages (); + if (!eo_pages) { + return std::vector (); + } else { + std::vector pages; + for (auto p = eo_pages->pages ().begin (); p != eo_pages->pages ().end (); ++p) { + if ((*p)->plugin_declaration () == plugin_declaration ()) { + pages.push_back (*p); + } + } + return pages; + } +} + +lay::EditorOptionsPage * +EditorServiceBase::focus_page () +{ + auto pages = editor_options_pages (); + for (auto p = pages.begin (); p != pages.end (); ++p) { + if ((*p)->is_focus_page ()) { + return *p; + } + } + + return 0; +} + +bool +EditorServiceBase::key_event (unsigned int key, unsigned int buttons) +{ + if (is_active () && key == Qt::Key_Tab && buttons == 0) { + focus_page_open (); + return true; + } else { + return false; + } +} + +int +EditorServiceBase::focus_page_open () +{ + auto fp = focus_page (); + return fp ? fp->show () : 0; } void EditorServiceBase::show_error (tl::Exception &ex) { tl::error << ex.msg (); -#if defined(HAVE_QT) QMessageBox::critical (ui ()->widget (), tr ("Error"), tl::to_qstring (ex.msg ())); -#endif } +#else + +bool +EditorServiceBase::key_event (unsigned int key, unsigned int buttons) +{ + return false; +} + +void +EditorServiceBase::show_error (tl::Exception &ex) +{ + tl::error << ex.msg (); +} + +int +EditorServiceBase::focus_page_open () +{ + return 0; +} + +#endif + } diff --git a/src/laybasic/laybasic/layEditorServiceBase.h b/src/laybasic/laybasic/layEditorServiceBase.h index 0b8cbd854..d23b37755 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.h +++ b/src/laybasic/laybasic/layEditorServiceBase.h @@ -46,7 +46,12 @@ class LAYBASIC_PUBLIC EditorServiceBase /** * @brief Constructor */ - EditorServiceBase (lay::LayoutViewBase *view); + EditorServiceBase (lay::LayoutViewBase *view = 0); + + /** + * @brief Initialize after constructor was called with null view pointer + */ + void init (lay::LayoutViewBase *view); /** * @brief Destructor @@ -69,6 +74,14 @@ class LAYBASIC_PUBLIC EditorServiceBase return this; } + /** + * @brief Gets a value indicating whether the plugin is active + */ + bool is_active () const + { + return m_active; + } + /** * @brief Adds a mouse cursor to the given point */ @@ -97,7 +110,7 @@ class LAYBASIC_PUBLIC EditorServiceBase /** * @brief Provides a nice mouse tracking cursor from the given snap details */ - void mouse_cursor_from_snap_details (const lay::PointSnapToObjectResult &snap_details); + void mouse_cursor_from_snap_details (const lay::PointSnapToObjectResult &snap_details, bool noclear = false); /** * @brief Gets the tracking cursor color @@ -136,10 +149,141 @@ class LAYBASIC_PUBLIC EditorServiceBase */ void show_error (tl::Exception &ex); -protected: + /** + * @brief Menu command handler + */ + virtual void menu_activated (const std::string & /*symbol*/) + { + // .. this implementation does nothing .. + } + + /** + * @brief Sets a configuration option + */ virtual bool configure (const std::string &name, const std::string &value); + + /** + * @brief Configuration finalization + */ + virtual void config_finalize () + { + lay::Plugin::config_finalize (); + } + + /** + * @brief Called when the plugin is deactivated + */ virtual void deactivated (); + /** + * @brief Called when the plugin is activated + */ + virtual void activated (); + + /** + * @brief Key event handler + */ + virtual bool key_event (unsigned int /*key*/, unsigned int /*buttons*/); + + /** + * @brief Mouse press event handler + */ + virtual bool mouse_press_event (const db::DPoint & /*p*/, unsigned int /*buttons*/, bool /*prio*/) + { + return false; + } + + /** + * @brief Mouse single-click event handler + */ + virtual bool mouse_click_event (const db::DPoint & /*p*/, unsigned int /*buttons*/, bool /*prio*/) + { + return false; + } + + /** + * @brief Mouse double-click event handler + */ + virtual bool mouse_double_click_event (const db::DPoint & /*p*/, unsigned int /*buttons*/, bool /*prio*/) + { + return false; + } + + /** + * @brief Mouse leave event handler + */ + virtual bool leave_event (bool /*prio*/) + { + return false; + } + + /** + * @brief Mouse enter event handler + */ + virtual bool enter_event (bool /*prio*/) + { + return false; + } + + /** + * @brief Mouse move event handler + */ + virtual bool mouse_move_event (const db::DPoint & /*p*/, unsigned int /*buttons*/, bool /*prio*/) + { + return false; + } + + /** + * @brief Mouse release event handler + */ + virtual bool mouse_release_event (const db::DPoint & /*p*/, unsigned int /*buttons*/, bool /*prio*/) + { + return false; + } + + /** + * @brief Wheel event handler + */ + virtual bool wheel_event (int /*delta*/, bool /*horizontal*/, const db::DPoint & /*p*/, unsigned int /*buttons*/, bool /*prio*/) + { + return false; + } + + /** + * @brief Updates the internal data after a coordinate system change for example + */ + virtual void update () + { + // The default implementation does nothing + } + + /** + * @brief This method is called when some mouse dragging operation should be cancelled + */ + virtual void drag_cancel () + { + // The default implementation does nothing + } + + /** + * @brief Gets called when the focus page opens + * + * The default implementation will call fp->show() and return its return value. + */ + virtual int focus_page_open (); + +#if defined(HAVE_QT) + /** + * @brief Gets the editor options pages associated with this plugin + */ + std::vector editor_options_pages (); + + /** + * @brief Gets the focus page or 0 if there is none + */ + lay::EditorOptionsPage *focus_page (); +#endif + private: // The marker representing the mouse cursor lay::LayoutViewBase *mp_view; @@ -148,6 +292,7 @@ class LAYBASIC_PUBLIC EditorServiceBase bool m_cursor_enabled; bool m_has_tracking_position; db::DPoint m_tracking_position; + bool m_active; }; } diff --git a/src/edt/edt/edtUtils.cc b/src/laybasic/laybasic/layEditorUtils.cc similarity index 95% rename from src/edt/edt/edtUtils.cc rename to src/laybasic/laybasic/layEditorUtils.cc index 1e16bb31f..377fe8f86 100644 --- a/src/edt/edt/edtUtils.cc +++ b/src/laybasic/laybasic/layEditorUtils.cc @@ -25,8 +25,7 @@ #include "dbLayout.h" #include "dbLibrary.h" -#include "edtUtils.h" -#include "edtService.h" +#include "layEditorUtils.h" #include "layCellView.h" #include "layLayoutViewBase.h" @@ -38,10 +37,35 @@ # include #endif -namespace edt { +namespace lay +{ // ------------------------------------------------------------- +int snap_range_pixels () +{ + return 8; // TODO: make variable +} + +// Convert buttons to an angle constraint +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; + } + } +} + std::string pcell_parameters_to_string (const std::map ¶meters) { std::string param; diff --git a/src/edt/edt/edtUtils.h b/src/laybasic/laybasic/layEditorUtils.h similarity index 75% rename from src/edt/edt/edtUtils.h rename to src/laybasic/laybasic/layEditorUtils.h index 15a98859f..50cef1179 100644 --- a/src/edt/edt/edtUtils.h +++ b/src/laybasic/laybasic/layEditorUtils.h @@ -21,15 +21,18 @@ */ -#ifndef HDR_edtUtils -#define HDR_edtUtils +#ifndef HDR_layEditorUtils +#define HDR_layEditorUtils #include #include #include #include +#include "laybasicCommon.h" + #include "layObjectInstPath.h" +#include "laySnap.h" #include "dbInstElement.h" #include "dbClipboardData.h" @@ -38,23 +41,34 @@ namespace lay { - class LayoutViewBase; - class Dispatcher; -} -namespace edt { +class LayoutViewBase; +class Dispatcher; + +// ------------------------------------------------------------- -class Service; +/** + * @brief Gets the snap range in pixels + */ +LAYBASIC_PUBLIC int snap_range_pixels (); + +/** + * @brief Convert a button flag set to an angle constraint + * + * This implements the standard modifiers for angle constraints - i.e. + * ortho for "Shift". + */ +LAYBASIC_PUBLIC lay::angle_constraint_type ac_from_buttons (unsigned int buttons); /** * @brief Serializes PCell parameters to a string */ -std::string pcell_parameters_to_string (const std::map ¶meters); +LAYBASIC_PUBLIC std::string pcell_parameters_to_string (const std::map ¶meters); /** * @brief Deserializes PCell parameters from a string */ -std::map pcell_parameters_from_string (const std::string &s); +LAYBASIC_PUBLIC std::map pcell_parameters_from_string (const std::string &s); /** * @brief Fetch PCell parameters from a cell and merge the guiding shapes into them @@ -64,6 +78,7 @@ std::map pcell_parameters_from_string (const std::stri * @param parameters_for_pcell Will receive the parameters * @return true, if the cell is a PCell and parameters have been fetched */ +LAYBASIC_PUBLIC bool get_parameters_from_pcell_and_guiding_shapes (db::Layout *layout, db::cell_index_type cell_index, db::pcell_parameters_type ¶meters_for_pcell); @@ -71,23 +86,14 @@ get_parameters_from_pcell_and_guiding_shapes (db::Layout *layout, db::cell_index /** * @brief Request to make the given layer the current one (asks whether to create the layer if needed) */ +LAYBASIC_PUBLIC bool set_or_request_current_layer (lay::LayoutViewBase *view, const db::LayerProperties &lp, unsigned int cv_index, bool make_current = true); -/** - * @brief A helper class that identifies clipboard data for edt:: - */ -class ClipboardData - : public db::ClipboardData -{ -public: - ClipboardData () { } -}; - /** * @brief A cache for the transformation variants for a certain layer and cell view index for a lay::LayoutView */ -class TransformationVariants +class LAYBASIC_PUBLIC TransformationVariants { public: TransformationVariants (const lay::LayoutViewBase *view, bool per_cv_and_layer = true, bool per_cv = true); diff --git a/src/laybasic/laybasic/layLayoutViewBase.cc b/src/laybasic/laybasic/layLayoutViewBase.cc index d743b860c..50a160e5d 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.cc +++ b/src/laybasic/laybasic/layLayoutViewBase.cc @@ -398,21 +398,6 @@ LayoutViewBase::init (db::Manager *mgr) mp_canvas = new lay::LayoutCanvas (this); - // occupy services and editables: - // these services get deleted by the canvas destructor automatically: - if ((m_options & LV_NoTracker) == 0) { - mp_tracker = new lay::MouseTracker (this); - } - if ((m_options & LV_NoZoom) == 0) { - mp_zoom_service = new lay::ZoomService (this); - } - if ((m_options & LV_NoSelection) == 0) { - mp_selection_service = new lay::SelectionService (this); - } - if ((m_options & LV_NoMove) == 0) { - mp_move_service = new lay::MoveService (this); - } - create_plugins (); } @@ -602,20 +587,41 @@ void LayoutViewBase::create_plugins (const lay::PluginDeclaration *except_this) clear_plugins (); // create the plugins - for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ) { - if (&*cls != except_this) { + // NOTE: during "create_plugin" a plugin may be unregistered, so don't increment the iterator after + auto current = cls.operator-> (); + std::string current_name = cls.current_name (); + ++cls; + + if (current != except_this) { // TODO: clean solution. The following is a HACK: - if (cls.current_name () == "ant::Plugin" || cls.current_name () == "img::Plugin") { + if (current_name == "ant::Plugin" || current_name == "img::Plugin") { // ant and img are created always - create_plugin (&*cls); + create_plugin (current); + } else if (current_name == "laybasic::MouseTrackerPlugin") { + if ((m_options & LV_NoTracker) == 0) { + mp_tracker = dynamic_cast (create_plugin (current)); + } + } else if (current_name == "laybasic::MoveServicePlugin") { + if ((m_options & LV_NoMove) == 0) { + mp_move_service = dynamic_cast (create_plugin (current)); + } + } else if (current_name == "laybasic::SelectionServicePlugin") { + if ((m_options & LV_NoSelection) == 0) { + mp_selection_service = dynamic_cast (create_plugin (current)); + } + } else if (current_name == "laybasic::ZoomServicePlugin") { + if ((m_options & LV_NoZoom) == 0) { + mp_zoom_service = dynamic_cast (create_plugin (current)); + } } else if ((options () & LV_NoPlugins) == 0) { // others: only create unless LV_NoPlugins is set - create_plugin (&*cls); - } else if ((options () & LV_NoGrid) == 0 && cls.current_name () == "GridNetPlugin") { + create_plugin (current); + } else if ((options () & LV_NoGrid) == 0 && current_name == "lay::GridNetPlugin") { // except grid net plugin which is created on request - create_plugin (&*cls); + create_plugin (current); } } @@ -685,6 +691,12 @@ LayoutViewBase::message (const std::string & /*s*/, int /*timeout*/) // .. nothing yet .. } +void +LayoutViewBase::set_focus () +{ + // .. nothing yet .. +} + bool LayoutViewBase::is_dirty () const { @@ -763,14 +775,6 @@ LayoutViewBase::configure (const std::string &name, const std::string &value) { lay::Dispatcher::configure (name, value); - if (mp_move_service && mp_move_service->configure (name, value)) { - return true; - } - - if (mp_tracker && mp_tracker->configure (name, value)) { - return true; - } - if (name == cfg_default_lyp_file) { m_def_lyp_file = value; @@ -1419,14 +1423,6 @@ LayoutViewBase::config_finalize () void LayoutViewBase::enable_edits (bool enable) { - // enable or disable these services: - if (mp_selection_service) { - mp_selection_service->enable (enable); - } - if (mp_move_service) { - mp_move_service->enable (enable); - } - // enable or disable the services that implement "lay::ViewService" for (std::vector::iterator p = mp_plugins.begin (); p != mp_plugins.end (); ++p) { lay::ViewService *svc = (*p)->view_service_interface (); @@ -1965,6 +1961,22 @@ LayoutViewBase::set_properties (unsigned int index, const LayerPropertiesList &p } } +void +LayoutViewBase::clear_layers (unsigned int index) +{ + LayerPropertiesList ll; + ll.set_name (get_properties (index).name ()); + set_properties (index, ll); +} + +void +LayoutViewBase::clear_layers () +{ + LayerPropertiesList ll; + ll.set_name (get_properties ().name ()); + set_properties (ll); +} + void LayoutViewBase::expand_properties () { @@ -4848,13 +4860,6 @@ LayoutViewBase::background_color (tl::Color c) do_set_background_color (c, contrast); - if (mp_selection_service) { - mp_selection_service->set_colors (c, contrast); - } - if (mp_zoom_service) { - mp_zoom_service->set_colors (c, contrast); - } - // Set the color for all ViewService interfaces for (std::vector::iterator p = mp_plugins.begin (); p != mp_plugins.end (); ++p) { lay::ViewService *svc = (*p)->view_service_interface (); @@ -5517,7 +5522,7 @@ LayoutViewBase::paste_interactive (bool transient_mode) // operations. trans->close (); - if (mp_move_service && mp_move_service->begin_move (trans.release (), transient_mode)) { + if (mp_move_service && mp_move_service->start_move (trans.release (), transient_mode)) { switch_mode (-1); // move mode } } @@ -5796,20 +5801,12 @@ LayoutViewBase::mode (int m) m_mode = m; mp_active_plugin = 0; - if (m > 0) { - - for (std::vector::iterator p = mp_plugins.begin (); p != mp_plugins.end (); ++p) { - if ((*p)->plugin_declaration ()->id () == m) { - mp_active_plugin = *p; - mp_canvas->activate ((*p)->view_service_interface ()); - break; - } + for (std::vector::iterator p = mp_plugins.begin (); p != mp_plugins.end (); ++p) { + if ((*p)->plugin_declaration ()->id () == m) { + mp_active_plugin = *p; + mp_canvas->activate ((*p)->view_service_interface ()); + break; } - - } else if (m == 0 && mp_selection_service) { - mp_canvas->activate (mp_selection_service); - } else if (m == -1 && mp_move_service) { - mp_canvas->activate (mp_move_service); } } diff --git a/src/laybasic/laybasic/layLayoutViewBase.h b/src/laybasic/laybasic/layLayoutViewBase.h index 4b839ed61..eeda5619a 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.h +++ b/src/laybasic/laybasic/layLayoutViewBase.h @@ -157,8 +157,8 @@ struct LAYBASIC_PUBLIC LayerDisplayProperties * It manages the layer display list, bookmark list etc. */ class LAYBASIC_PUBLIC LayoutViewBase : - public lay::Editables, - public lay::Dispatcher + public lay::Dispatcher, // needs to be first as it is the GSI base class + public lay::Editables { public: typedef lay::CellView::unspecific_cell_path_type cell_path_type; @@ -295,6 +295,11 @@ class LAYBASIC_PUBLIC LayoutViewBase : */ virtual void message (const std::string &s = "", int timeout = 10); + /** + * @brief Sets the keyboard focus to the view + */ + virtual void set_focus (); + /** * @brief The "dirty" flag indicates that one of the layout has been modified * @@ -510,18 +515,12 @@ class LAYBASIC_PUBLIC LayoutViewBase : /** * @brief Clear the given layer view list */ - void clear_layers (unsigned int index) - { - set_properties (index, LayerPropertiesList ()); - } + void clear_layers (unsigned int index); /** * @brief Clear the current layer view list */ - void clear_layers () - { - set_properties (LayerPropertiesList ()); - } + void clear_layers (); /** * @brief Access the current layer properties list diff --git a/src/laybasic/laybasic/layMouseTracker.cc b/src/laybasic/laybasic/layMouseTracker.cc index 981c72c62..11c128299 100644 --- a/src/laybasic/laybasic/layMouseTracker.cc +++ b/src/laybasic/laybasic/layMouseTracker.cc @@ -31,7 +31,7 @@ namespace lay { MouseTracker::MouseTracker (lay::LayoutViewBase *view) - : lay::ViewService (view->canvas ()), mp_view (view), + : lay::ViewService (view->canvas ()), lay::Plugin (view), mp_view (view), m_cursor_color (tl::Color ()), m_cursor_line_style (0), m_cursor_enabled (false) { ui ()->grab_mouse (this, false); @@ -129,5 +129,31 @@ MouseTracker::mouse_move_event (const db::DPoint &p, unsigned int /*buttons*/, b return false; } -} // namespace lay +// ---------------------------------------------------------------------------- + +// NOTE: configuration currently is not declared here. +// Same for the configuration pages. + +class MouseTrackerDeclaration + : public lay::PluginDeclaration +{ +public: + MouseTrackerDeclaration () + { + // .. nothing yet .. + } + virtual lay::Plugin *create_plugin (db::Manager * /*manager*/, lay::Dispatcher * /*dispatcher*/, lay::LayoutViewBase *view) const + { + return new MouseTracker (view); + } + + virtual bool enable_catchall_editor_options_pages () const + { + return false; + } +}; + +static tl::RegisteredClass tracker_decl (new MouseTrackerDeclaration (), -1000, "laybasic::MouseTrackerPlugin"); + +} // namespace lay diff --git a/src/laybasic/laybasic/layMouseTracker.h b/src/laybasic/laybasic/layMouseTracker.h index 554da9be6..d7a8a5934 100644 --- a/src/laybasic/laybasic/layMouseTracker.h +++ b/src/laybasic/laybasic/layMouseTracker.h @@ -26,6 +26,7 @@ #include "layViewObject.h" #include "layMarker.h" +#include "layPlugin.h" #include "tlObject.h" class QMouseEvent; @@ -36,7 +37,7 @@ class LayoutCanvas; class LayoutViewBase; class MouseTracker - : public lay::ViewService + : public lay::ViewService, public lay::Plugin { public: MouseTracker (lay::LayoutViewBase *view); @@ -45,6 +46,11 @@ class MouseTracker bool leave_event (bool prio); bool configure (const std::string &name, const std::string &value); + lay::ViewService *view_service_interface () + { + return this; + } + private: lay::LayoutViewBase *mp_view; tl::shared_collection mp_markers; diff --git a/src/laybasic/laybasic/layMove.cc b/src/laybasic/laybasic/layMove.cc index 7675b95c4..a2a6ba96c 100644 --- a/src/laybasic/laybasic/layMove.cc +++ b/src/laybasic/laybasic/layMove.cc @@ -20,11 +20,10 @@ */ - - #include "layMove.h" #include "layEditable.h" #include "layLayoutViewBase.h" +#include "layEditorUtils.h" #include "laySelector.h" #include "laybasicConfig.h" @@ -35,7 +34,7 @@ namespace lay // MoveService implementation MoveService::MoveService (lay::LayoutViewBase *view) - : lay::ViewService (view->canvas ()), + : lay::EditorServiceBase (view), m_dragging (false), m_dragging_transient (false), mp_editables (view), @@ -50,44 +49,36 @@ MoveService::~MoveService () drag_cancel (); } -void +void MoveService::deactivated () { + EditorServiceBase::deactivated (); m_shift = db::DPoint (); - mp_view->clear_transient_selection (); + mp_editables->clear_transient_selection (); drag_cancel (); } -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; - } - } -} - bool MoveService::configure (const std::string &name, const std::string &value) { + if (lay::EditorServiceBase::configure (name, value)) { + return true; + } + if (name == cfg_grid) { tl::from_string (value, m_global_grid); } + return false; // not taken } bool -MoveService::key_event (unsigned int key, unsigned int /*buttons*/) +MoveService::key_event (unsigned int key, unsigned int buttons) { + if (lay::EditorServiceBase::key_event (key, buttons)) { + return true; + } + double dx = 0.0, dy = 0.0; if (int (key) == lay::KeyDown) { dy = -1.0; @@ -129,6 +120,16 @@ MoveService::key_event (unsigned int key, unsigned int /*buttons*/) } } +int +MoveService::focus_page_open () +{ + // This method is called on "Tab" by "key_event". "fp" is null as we don't have a focus page registered. + if (is_active () && dispatcher ()) { + dispatcher ()->menu_activated ("cm_sel_move"); + } + return 0; +} + bool MoveService::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) { @@ -240,7 +241,7 @@ MoveService::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool } bool -MoveService::begin_move (db::Transaction *transaction, bool transient_selection) +MoveService::start_move (db::Transaction *transaction, bool transient_selection) { if (m_dragging) { return false; @@ -308,7 +309,7 @@ MoveService::handle_click (const db::DPoint &p, unsigned int buttons, bool drag_ ui ()->hover_reset (); - mp_view->clear_transient_selection (); + mp_editables->clear_transient_selection (); m_dragging = true; m_dragging_transient = drag_transient; @@ -366,5 +367,24 @@ MoveService::finish () } } -} +// ---------------------------------------------------------------------------- +class MoveServiceDeclaration + : public lay::PluginDeclaration +{ +public: + MoveServiceDeclaration () + : lay::PluginDeclaration (-1) + { + // .. nothing yet .. + } + + virtual lay::Plugin *create_plugin (db::Manager * /*manager*/, lay::Dispatcher * /*dispatcher*/, lay::LayoutViewBase *view) const + { + return new MoveService (view); + } +}; + +static tl::RegisteredClass move_service_decl (new MoveServiceDeclaration (), -970, "laybasic::MoveServicePlugin"); + +} diff --git a/src/laybasic/laybasic/layMove.h b/src/laybasic/laybasic/layMove.h index 589cea374..a8cd8cdb9 100644 --- a/src/laybasic/laybasic/layMove.h +++ b/src/laybasic/laybasic/layMove.h @@ -24,25 +24,25 @@ #define HDR_layMove #include "laybasicCommon.h" +#include "layEditorServiceBase.h" #include "dbManager.h" -#include "layViewObject.h" #include namespace lay { -class Editables; class LayoutViewBase; class LAYBASIC_PUBLIC MoveService : - public lay::ViewService + public lay::EditorServiceBase { public: MoveService (lay::LayoutViewBase *view); ~MoveService (); + bool start_move (db::Transaction *transaction = 0, bool transient_selection = false); + bool configure (const std::string &name, const std::string &value); - bool begin_move (db::Transaction *transaction = 0, bool transient_selection = false); void finish (); void cancel (); @@ -56,6 +56,7 @@ class LAYBASIC_PUBLIC MoveService : virtual bool key_event (unsigned int key, unsigned int buttons); virtual void drag_cancel (); virtual void deactivated (); + int focus_page_open (); bool handle_click (const db::DPoint &p, unsigned int buttons, bool drag_transient, db::Transaction *transaction); diff --git a/src/laybasic/laybasic/layPlugin.cc b/src/laybasic/laybasic/layPlugin.cc index dae465c8d..842351536 100644 --- a/src/laybasic/laybasic/layPlugin.cc +++ b/src/laybasic/laybasic/layPlugin.cc @@ -55,6 +55,12 @@ PluginDeclaration::PluginDeclaration () // .. nothing yet .. } +PluginDeclaration::PluginDeclaration (int id) + : m_id (id), m_editable_enabled (true) +{ + // .. nothing yet .. +} + PluginDeclaration::~PluginDeclaration () { if (Dispatcher::instance ()) { @@ -309,8 +315,23 @@ PluginDeclaration::register_plugin () // Plugin implementation Plugin::Plugin (Plugin *parent, bool standalone) - : mp_parent (parent), mp_plugin_declaration (0), dm_finalize_config (this, &lay::Plugin::config_end), m_standalone (standalone) + : mp_parent (0), mp_plugin_declaration (0), dm_finalize_config (this, &lay::Plugin::config_end), m_standalone (false) +{ + init (parent, standalone); +} + +Plugin::Plugin () + : mp_parent (0), mp_plugin_declaration (0), dm_finalize_config (this, &lay::Plugin::config_end), m_standalone (false) { + // .. nothing yet (waiting for init) .. +} + +void +Plugin::init (Plugin *parent, bool standalone) +{ + mp_parent = parent; + m_standalone = standalone; + if (! parent) { if (! standalone) { // load the root with the default configuration @@ -325,6 +346,7 @@ Plugin::Plugin (Plugin *parent, bool standalone) } } + Plugin::~Plugin () { if (mp_parent) { diff --git a/src/laybasic/laybasic/layPlugin.h b/src/laybasic/laybasic/layPlugin.h index 989bd233b..2d5ee32d4 100644 --- a/src/laybasic/laybasic/layPlugin.h +++ b/src/laybasic/laybasic/layPlugin.h @@ -160,6 +160,11 @@ Q_OBJECT */ PluginDeclaration (); + /** + * @brief Constructor with a fixed ID + */ + PluginDeclaration (int id); + /** * @brief Destructor */ @@ -331,6 +336,18 @@ Q_OBJECT { // .. no pages in the default implementation .. } + + /** + * @brief Gets a value indicating whether "catchall" editor options pages shall be included + * + * "catchall" editor options pages are ones that are unspecific and render a null "plugin_declaration". + * A plugin can choose to include these pages if it listens to global configuration events. + * Otherwise it should return false here to suppress these pages. + */ + virtual bool enable_catchall_editor_options_pages () const + { + return true; + } #endif /** @@ -504,6 +521,18 @@ class LAYBASIC_PUBLIC Plugin */ Plugin (Plugin *parent, bool standalone = false); + /** + * @brief The default constructor + * + * This constructor needs to be followed by init() + */ + Plugin (); + + /** + * @brief Initialization, following the default constructor + */ + void init (Plugin *parent, bool standalone = false); + /** * @brief The destructor */ diff --git a/src/laybasic/laybasic/laySelector.cc b/src/laybasic/laybasic/laySelector.cc index c3c292d99..8a86a6048 100644 --- a/src/laybasic/laybasic/laySelector.cc +++ b/src/laybasic/laybasic/laySelector.cc @@ -43,6 +43,7 @@ SelectionService::SelectionService (lay::LayoutViewBase *view) : QObject (), #endif lay::ViewService (view->canvas ()), + lay::Plugin (view), mp_view (view), mp_box (0), m_color (0), @@ -317,4 +318,29 @@ SelectionService::begin (const db::DPoint &pos) ui ()->grab_mouse (this, true); } +// ---------------------------------------------------------------------------- + +class SelectionServiceDeclaration + : public lay::PluginDeclaration +{ +public: + SelectionServiceDeclaration () + : lay::PluginDeclaration (0) + { + // .. nothing yet .. + } + + virtual lay::Plugin *create_plugin (db::Manager * /*manager*/, lay::Dispatcher * /*dispatcher*/, lay::LayoutViewBase *view) const + { + return new SelectionService (view); + } + + virtual bool enable_catchall_editor_options_pages () const + { + return false; + } +}; + +static tl::RegisteredClass selection_service_decl (new SelectionServiceDeclaration (), -980, "laybasic::SelectionServicePlugin"); + } diff --git a/src/laybasic/laybasic/laySelector.h b/src/laybasic/laybasic/laySelector.h index b15fe0400..d4f29dbd3 100644 --- a/src/laybasic/laybasic/laySelector.h +++ b/src/laybasic/laybasic/laySelector.h @@ -29,6 +29,7 @@ #include "layViewObject.h" #include "layEditable.h" +#include "layPlugin.h" #if defined (HAVE_QT) # include @@ -45,7 +46,8 @@ class LAYBASIC_PUBLIC SelectionService : #if defined (HAVE_QT) public QObject, #endif - public lay::ViewService + public lay::ViewService, + public lay::Plugin { #if defined (HAVE_QT) Q_OBJECT @@ -55,6 +57,11 @@ Q_OBJECT SelectionService (lay::LayoutViewBase *view); ~SelectionService (); + lay::ViewService *view_service_interface () + { + return this; + } + void set_colors (tl::Color background, tl::Color color); void begin (const db::DPoint &pos); diff --git a/src/laybasic/laybasic/layViewObject.cc b/src/laybasic/laybasic/layViewObject.cc index fe9329fcd..3cfa61c04 100644 --- a/src/laybasic/laybasic/layViewObject.cc +++ b/src/laybasic/laybasic/layViewObject.cc @@ -191,8 +191,15 @@ ViewObject::freeze () // ViewService implementation ViewService::ViewService (ViewObjectUI *widget) - : mp_widget (widget), m_abs_grab (false), m_enabled (true) + : mp_widget (0), m_abs_grab (false), m_enabled (true) { + init (widget); +} + +void +ViewService::init (ViewObjectUI *widget) +{ + mp_widget = widget; if (widget) { widget->register_service (this); } diff --git a/src/laybasic/laybasic/layViewObject.h b/src/laybasic/laybasic/layViewObject.h index 889eae89b..7664b8f78 100644 --- a/src/laybasic/laybasic/layViewObject.h +++ b/src/laybasic/laybasic/layViewObject.h @@ -104,6 +104,11 @@ class LAYBASIC_PUBLIC ViewService */ ViewService (ViewObjectUI *widget = 0); + /** + * @brief Initialization, can follow default constructor + */ + void init (ViewObjectUI *widget); + /** * @brief Destructor */ diff --git a/src/laybasic/laybasic/layZoomBox.cc b/src/laybasic/laybasic/layZoomBox.cc index 221849169..064209ee2 100644 --- a/src/laybasic/laybasic/layZoomBox.cc +++ b/src/laybasic/laybasic/layZoomBox.cc @@ -32,7 +32,7 @@ namespace lay // ZoomService implementation ZoomService::ZoomService (lay::LayoutViewBase *view) - : lay::ViewService (view->canvas ()), + : lay::ViewService (view->canvas ()), lay::Plugin (view), mp_view (view), mp_box (0), m_color (0) @@ -282,5 +282,28 @@ ZoomService::begin (const db::DPoint &pos) ui ()->grab_mouse (this, true); } -} +// ---------------------------------------------------------------------------- + +class ZoomServiceDeclaration + : public lay::PluginDeclaration +{ +public: + ZoomServiceDeclaration () + { + // .. nothing yet .. + } + + virtual lay::Plugin *create_plugin (db::Manager * /*manager*/, lay::Dispatcher * /*dispatcher*/, lay::LayoutViewBase *view) const + { + return new ZoomService (view); + } + virtual bool enable_catchall_editor_options_pages () const + { + return false; + } +}; + +static tl::RegisteredClass zoom_service_decl (new ZoomServiceDeclaration (), -990, "laybasic::ZoomServicePlugin"); + +} diff --git a/src/laybasic/laybasic/layZoomBox.h b/src/laybasic/laybasic/layZoomBox.h index 2530eec98..93fe9bbec 100644 --- a/src/laybasic/laybasic/layZoomBox.h +++ b/src/laybasic/laybasic/layZoomBox.h @@ -26,6 +26,7 @@ #define HDR_layZoomBox #include "layViewObject.h" +#include "layPlugin.h" namespace lay { @@ -35,7 +36,7 @@ class LayoutCanvas; class RubberBox; class LAYBASIC_PUBLIC ZoomService - : public lay::ViewService + : public lay::ViewService, public lay::Plugin { public: ZoomService (lay::LayoutViewBase *view); @@ -45,6 +46,11 @@ class LAYBASIC_PUBLIC ZoomService void begin (const db::DPoint &pos); void begin_pan (const db::DPoint &pos); + lay::ViewService *view_service_interface () + { + return this; + } + private: virtual bool mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio); virtual bool mouse_release_event (const db::DPoint &p, unsigned int buttons, bool prio); diff --git a/src/laybasic/laybasic/laybasic.pro b/src/laybasic/laybasic/laybasic.pro index 4262ca687..f6998f621 100644 --- a/src/laybasic/laybasic/laybasic.pro +++ b/src/laybasic/laybasic/laybasic.pro @@ -28,13 +28,16 @@ DEFINES += MAKE_LAYBASIC_LIBRARY SOURCES += \ gsiDeclLayLayers.cc \ + gsiDeclLayDispatcher.cc \ gsiDeclLayLayoutViewBase.cc \ gsiDeclLayMarker.cc \ gsiDeclLayMenu.cc \ - gsiDeclLayPlugin.cc \ gsiDeclLayTlAdded.cc \ gsiDeclLayRdbAdded.cc \ layAbstractMenu.cc \ + layEditorOptionsPage.cc \ + layEditorOptionsPages.cc \ + layEditorUtils.cc \ layLayoutViewConfig.cc \ layMargin.cc \ laybasicForceLink.cc \ @@ -87,6 +90,9 @@ SOURCES += \ layUtils.cc \ HEADERS += \ + layEditorOptionsPage.h \ + layEditorOptionsPages.h \ + layEditorUtils.h \ layMargin.h \ laybasicConfig.h \ laybasicForceLink.h \ diff --git a/src/layui/layui/LayoutViewConfigPage7.ui b/src/layui/layui/LayoutViewConfigPage7.ui index 7f7939b75..e2a57aebf 100644 --- a/src/layui/layui/LayoutViewConfigPage7.ui +++ b/src/layui/layui/LayoutViewConfigPage7.ui @@ -390,6 +390,14 @@ + + oversampling + subres_mode + highres_mode + default_font_size + global_trans + def_depth + diff --git a/src/layui/layui/LibraryCellSelectionForm.ui b/src/layui/layui/LibraryCellSelectionForm.ui index aadc63877..d592336ec 100644 --- a/src/layui/layui/LibraryCellSelectionForm.ui +++ b/src/layui/layui/LibraryCellSelectionForm.ui @@ -240,6 +240,15 @@ p, li { white-space: pre-wrap; }
layWidgets.h
+ + lib_cb + le_cell_name + find_next + lv_cells + cb_show_all_cells + ok_button + cancel_button + diff --git a/src/layui/layui/MarkerBrowserConfigPage.ui b/src/layui/layui/MarkerBrowserConfigPage.ui index 227942cc4..9417066d1 100644 --- a/src/layui/layui/MarkerBrowserConfigPage.ui +++ b/src/layui/layui/MarkerBrowserConfigPage.ui @@ -144,6 +144,11 @@ 1 + + cbx_context + cbx_window + le_max_markers + diff --git a/src/layui/layui/MarkerBrowserConfigPage2.ui b/src/layui/layui/MarkerBrowserConfigPage2.ui index 94b14426c..47a49477d 100644 --- a/src/layui/layui/MarkerBrowserConfigPage2.ui +++ b/src/layui/layui/MarkerBrowserConfigPage2.ui @@ -1,7 +1,8 @@ - + + MarkerBrowserConfigPage2 - - + + 0 0 @@ -9,57 +10,57 @@ 174 - + Marker Database Browser - - - 9 - - + + 6 + + 9 + - - + + Marker Appearance - - + + 9 - + 6 - - - + + + With halo - + true - - + + - - + + - - - + + + pixel - + - + Qt::Horizontal - + 71 31 @@ -67,37 +68,35 @@ - - - - - 0 - 0 + + + + 0 0 - + pixel - - - + + + Line width - + - + Qt::Horizontal - + QSizePolicy::Fixed - + 41 31 @@ -105,40 +104,40 @@ - - - + + + Vertex size - - - + + + The color in which the markers are drawn - + - - - + + + Marker color - - - + + + Stipple - - - + + + @@ -148,7 +147,7 @@ - + lay::DitherPatternSelectionButton @@ -161,6 +160,13 @@
layWidgets.h
+ + color_pb + lw_le + stipple_pb + vs_le + halo_cb +
diff --git a/src/layui/layui/MoveOptionsDialog.ui b/src/layui/layui/MoveOptionsDialog.ui index 297f68ed3..f85443448 100644 --- a/src/layui/layui/MoveOptionsDialog.ui +++ b/src/layui/layui/MoveOptionsDialog.ui @@ -6,7 +6,7 @@ 0 0 - 233 + 240 168 @@ -17,7 +17,7 @@ 6 - + 9 @@ -26,12 +26,16 @@ Displacement - - 9 - 6 + + + + µm + + + @@ -45,13 +49,6 @@ - - - - µm - - - @@ -69,13 +66,6 @@ - - - - x - - - @@ -86,6 +76,13 @@ + + + + x + + + @@ -117,39 +114,70 @@ disp_x_le disp_y_le - buttonBox buttonBox - accepted() + rejected() MoveOptionsDialog - accept() + reject() - 248 - 254 + 316 + 260 - 157 + 286 274 buttonBox - rejected() + accepted() MoveOptionsDialog - reject() + accept() - 316 - 260 + 119 + 146 - 286 - 274 + 119 + 83 + + + + + disp_x_le + returnPressed() + MoveOptionsDialog + accept() + + + 119 + 53 + + + 119 + 83 + + + + + disp_y_le + returnPressed() + MoveOptionsDialog + accept() + + + 119 + 84 + + + 119 + 83 diff --git a/src/layui/layui/MoveToOptionsDialog.ui b/src/layui/layui/MoveToOptionsDialog.ui index 448a5e6b7..b61c7cf2b 100644 --- a/src/layui/layui/MoveToOptionsDialog.ui +++ b/src/layui/layui/MoveToOptionsDialog.ui @@ -7,7 +7,7 @@ 0 0 396 - 345 + 357 @@ -354,6 +354,13 @@ + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + @@ -369,44 +376,14 @@ 0 - - - - Qt::Horizontal - - - - 209 - 20 - - - - - - - - Ok - - - true - - - - - - - Cancel - - - - pushButton - pushButton_2 + x_le + y_le lt ct rt @@ -422,34 +399,34 @@ - pushButton - clicked() + buttonBox + accepted() MoveToOptionsDialog accept() - 237 - 203 + 197 + 323 - 147 - 81 + 197 + 175 - pushButton_2 - clicked() + buttonBox + rejected() MoveToOptionsDialog reject() - 325 - 202 + 197 + 323 - 325 - 57 + 197 + 175 diff --git a/src/layui/layui/NetlistBrowserConfigPage.ui b/src/layui/layui/NetlistBrowserConfigPage.ui index c24ae94ae..25c9219d7 100644 --- a/src/layui/layui/NetlistBrowserConfigPage.ui +++ b/src/layui/layui/NetlistBrowserConfigPage.ui @@ -138,6 +138,11 @@ + + cbx_window + le_window + le_max_markers +
diff --git a/src/layui/layui/NetlistBrowserConfigPage2.ui b/src/layui/layui/NetlistBrowserConfigPage2.ui index 9139b7fa9..9a5093e33 100644 --- a/src/layui/layui/NetlistBrowserConfigPage2.ui +++ b/src/layui/layui/NetlistBrowserConfigPage2.ui @@ -6,7 +6,7 @@ 0 0 - 649 + 667 351 @@ -529,6 +529,24 @@ Non-net objects are drawn using the highlight color.
layWidgets.h
+ + stipple_pb + lw_le + halo_cb + vs_le + color_pb + brightness_cb + brightness_sb + cycle_colors_cb + cc0 + cc1 + cc5 + cc6 + cc3 + cc7 + cc4 + cc2 + diff --git a/src/layui/layui/UserPropertiesForm.ui b/src/layui/layui/UserPropertiesForm.ui index 8faf01105..983221f8d 100644 --- a/src/layui/layui/UserPropertiesForm.ui +++ b/src/layui/layui/UserPropertiesForm.ui @@ -49,7 +49,7 @@ - 0 + 2 @@ -234,7 +234,13 @@ - buttonBox + mode_tab + prop_list + add_pb + remove_pb + edit_pb + text_edit + meta_info_list diff --git a/src/layui/layui/layEditorOptionsPages.cc b/src/layui/layui/layEditorOptionsPages.cc deleted file mode 100644 index 1a154d029..000000000 --- a/src/layui/layui/layEditorOptionsPages.cc +++ /dev/null @@ -1,218 +0,0 @@ - -/* - - KLayout Layout Viewer - Copyright (C) 2006-2025 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 - -*/ - -#if defined(HAVE_QT) - -#include "tlInternational.h" -#include "layEditorOptionsPages.h" -#include "tlExceptions.h" -#include "layPlugin.h" -#include "layLayoutViewBase.h" -#include "layQtTools.h" - -#include -#include -#include -#include -#include -#include - -namespace lay -{ - -// ------------------------------------------------------------------ -// EditorOptionsPages implementation - -struct EOPCompareOp -{ - bool operator() (lay::EditorOptionsPage *a, lay::EditorOptionsPage *b) const - { - return a->order () < b->order (); - } -}; - -EditorOptionsPages::EditorOptionsPages (QWidget *parent, const std::vector &pages, lay::Dispatcher *dispatcher) - : QFrame (parent), mp_dispatcher (dispatcher) -{ - QVBoxLayout *ly1 = new QVBoxLayout (this); - ly1->setContentsMargins (0, 0, 0, 0); - - mp_pages = new QTabWidget (this); - mp_pages->setSizePolicy (QSizePolicy (QSizePolicy::Ignored, QSizePolicy::Ignored)); - ly1->addWidget (mp_pages); - - m_pages = pages; - for (std::vector ::const_iterator p = m_pages.begin (); p != m_pages.end (); ++p) { - (*p)->set_owner (this); - } - - update (0); - setup (); -} - -EditorOptionsPages::~EditorOptionsPages () -{ - while (m_pages.size () > 0) { - delete m_pages.front (); - } -} - -void -EditorOptionsPages::focusInEvent (QFocusEvent * /*event*/) -{ - // Sends the focus to the current page's last focus owner - if (mp_pages->currentWidget () && mp_pages->currentWidget ()->focusWidget ()) { - mp_pages->currentWidget ()->focusWidget ()->setFocus (); - } -} - -bool EditorOptionsPages::has_content () const -{ - for (std::vector ::const_iterator p = m_pages.begin (); p != m_pages.end (); ++p) { - // NOTE: we ignore unspecific pages because they are always visible and don't contribute specific content - if ((*p)->active () && (*p)->plugin_declaration () != 0) { - return true; - } - } - return false; -} - -void -EditorOptionsPages::unregister_page (lay::EditorOptionsPage *page) -{ - std::vector pages; - for (std::vector ::const_iterator p = m_pages.begin (); p != m_pages.end (); ++p) { - if (*p != page) { - pages.push_back (*p); - } - } - m_pages = pages; - update (0); -} - -void -EditorOptionsPages::activate_page (lay::EditorOptionsPage *page) -{ - try { - if (page->active ()) { - page->setup (mp_dispatcher); - } - } catch (...) { - // catch any errors related to configuration file errors etc. - } - - update (page); -} - -void -EditorOptionsPages::update (lay::EditorOptionsPage *page) -{ - std::vector sorted_pages = m_pages; - std::sort (sorted_pages.begin (), sorted_pages.end (), EOPCompareOp ()); - - if (! page && m_pages.size () > 0) { - page = m_pages.back (); - } - - while (mp_pages->count () > 0) { - mp_pages->removeTab (0); - } - int index = -1; - for (std::vector ::iterator p = sorted_pages.begin (); p != sorted_pages.end (); ++p) { - if ((*p)->active ()) { - if ((*p) == page) { - index = mp_pages->count (); - } - mp_pages->addTab (*p, tl::to_qstring ((*p)->title ())); - } else { - (*p)->setParent (0); - } - } - if (index < 0) { - index = mp_pages->currentIndex (); - } - if (index >= int (mp_pages->count ())) { - index = mp_pages->count () - 1; - } - mp_pages->setCurrentIndex (index); - - setVisible (mp_pages->count () > 0); -} - -void -EditorOptionsPages::setup () -{ - try { - - for (std::vector ::iterator p = m_pages.begin (); p != m_pages.end (); ++p) { - if ((*p)->active ()) { - (*p)->setup (mp_dispatcher); - } - } - - // make the display consistent with the status (this is important for - // PCell parameters where the PCell may be asked to modify the parameters) - do_apply (); - - } catch (...) { - // catch any errors related to configuration file errors etc. - } -} - -void -EditorOptionsPages::do_apply () -{ - for (std::vector ::iterator p = m_pages.begin (); p != m_pages.end (); ++p) { - if ((*p)->active ()) { - // NOTE: we apply to the root dispatcher, so other dispatchers (views) get informed too. - (*p)->apply (mp_dispatcher->dispatcher ()); - } - } -} - -void -EditorOptionsPages::apply () -{ -BEGIN_PROTECTED - do_apply (); -END_PROTECTED_W (this) -} - -// ------------------------------------------------------------------ -// Indicates an error on a line edit - -template -static void configure_from_line_edit (lay::Dispatcher *dispatcher, QLineEdit *le, const std::string &cfg_name) -{ - try { - Value value = Value (0); - tl::from_string_ext (tl::to_string (le->text ()), value); - dispatcher->config_set (cfg_name, tl::to_string (value)); - lay::indicate_error (le, (tl::Exception *) 0); - } catch (tl::Exception &ex) { - lay::indicate_error (le, &ex); - } -} - -} - -#endif diff --git a/src/layui/layui/layLayoutViewFunctions.cc b/src/layui/layui/layLayoutViewFunctions.cc index ef359aaea..6f1417813 100644 --- a/src/layui/layui/layLayoutViewFunctions.cc +++ b/src/layui/layui/layLayoutViewFunctions.cc @@ -1191,6 +1191,8 @@ LayoutViewFunctions::cm_remove_unused () void LayoutViewFunctions::do_cm_duplicate (bool interactive) { + view ()->cancel_edits (); + // Do duplicate simply by concatenating copy & paste currently. // Save the clipboard state before in order to preserve the current content db::Clipboard saved_clipboard; @@ -1199,7 +1201,6 @@ LayoutViewFunctions::do_cm_duplicate (bool interactive) try { bool transient_mode = ! view ()->has_selection (); view ()->copy_view_objects (); - view ()->cancel_edits (); if (interactive) { view ()->paste_interactive (transient_mode); } else { @@ -1542,7 +1543,7 @@ void LayoutViewFunctions::cm_sel_move_interactive () { view ()->cancel_edits (); - if (view ()->move_service ()->begin_move ()) { + if (view ()->move_service ()->start_move ()) { view ()->switch_mode (-1); // move mode } } diff --git a/src/layui/layui/layui.pro b/src/layui/layui/layui.pro index 094e58410..cf30329d0 100644 --- a/src/layui/layui/layui.pro +++ b/src/layui/layui/layui.pro @@ -104,8 +104,6 @@ SOURCES = \ layEditStippleWidget.cc \ layEditStipplesForm.cc \ layEditorOptionsFrame.cc \ - layEditorOptionsPage.cc \ - layEditorOptionsPages.cc \ layFileDialog.cc \ layGenericSyntaxHighlighter.cc \ layHierarchyControlPanel.cc \ @@ -164,8 +162,6 @@ HEADERS = \ layEditStippleWidget.h \ layEditStipplesForm.h \ layEditorOptionsFrame.h \ - layEditorOptionsPage.h \ - layEditorOptionsPages.h \ layFileDialog.h \ layGenericSyntaxHighlighter.h \ layHierarchyControlPanel.h \ diff --git a/src/layview/layview/layGridNet.cc b/src/layview/layview/layGridNet.cc index 92fc3b6ab..c6ecb634c 100644 --- a/src/layview/layview/layGridNet.cc +++ b/src/layview/layview/layGridNet.cc @@ -129,7 +129,7 @@ GridNetPluginDeclaration::create_plugin (db::Manager *, Dispatcher *, lay::Layou return new lay::GridNet (view); } -static tl::RegisteredClass config_decl (new GridNetPluginDeclaration (), 2010, "GridNetPlugin"); +static tl::RegisteredClass config_decl (new GridNetPluginDeclaration (), 2010, "lay::GridNetPlugin"); // ------------------------------------------------------------ // Implementation of the GridNet object diff --git a/src/layview/layview/layLayoutView_qt.cc b/src/layview/layview/layLayoutView_qt.cc index d361b4601..4502aec59 100644 --- a/src/layview/layview/layLayoutView_qt.cc +++ b/src/layview/layview/layLayoutView_qt.cc @@ -1577,12 +1577,21 @@ LayoutView::message (const std::string &s, int timeout) } } +void +LayoutView::set_focus () +{ + if (canvas () && canvas ()->widget ()) { + canvas ()->widget ()->setFocus (Qt::TabFocusReason); + } +} + void LayoutView::mode (int m) { if (mode () != m) { LayoutViewBase::mode (m); activate_editor_option_pages (); + set_focus (); } } @@ -1591,18 +1600,7 @@ LayoutView::activate_editor_option_pages () { lay::EditorOptionsPages *eo_pages = editor_options_pages (); if (eo_pages) { - - // TODO: this is very inefficient as each "activate" will regenerate the tabs - for (std::vector::const_iterator op = eo_pages->pages ().begin (); op != eo_pages->pages ().end (); ++op) { - bool is_active = false; - if ((*op)->plugin_declaration () == 0) { - is_active = true; - } else if (active_plugin () && active_plugin ()->plugin_declaration () == (*op)->plugin_declaration ()) { - is_active = true; - } - (*op)->activate (is_active); - } - + eo_pages->activate (active_plugin ()); } } @@ -1610,10 +1608,12 @@ void LayoutView::switch_mode (int m) { if (mode () != m) { - mode (m); + LayoutViewBase::mode (m); + activate_editor_option_pages (); if (mp_widget) { mp_widget->emit_mode_change (m); } + set_focus (); } } diff --git a/src/layview/layview/layLayoutView_qt.h b/src/layview/layview/layLayoutView_qt.h index 59b0a1757..5867dbf60 100644 --- a/src/layview/layview/layLayoutView_qt.h +++ b/src/layview/layview/layLayoutView_qt.h @@ -183,6 +183,11 @@ class LAYVIEW_PUBLIC LayoutView */ void message (const std::string &s = "", int timeout = 10); + /** + * @brief Sets the keyboard focus to the view + */ + virtual void set_focus (); + /** * @brief Select a certain mode (by index) */ diff --git a/src/layview/unit_tests/layLayoutViewTests.cc b/src/layview/unit_tests/layLayoutViewTests.cc index 4cdf3b498..64b7b0cc0 100644 --- a/src/layview/unit_tests/layLayoutViewTests.cc +++ b/src/layview/unit_tests/layLayoutViewTests.cc @@ -173,6 +173,42 @@ TEST(4) EXPECT_EQ ((int) img.height (), 217); } +// options +TEST(5) +{ + std::unique_ptr lv; + + lv.reset (new lay::LayoutView (0, false, 0)); + EXPECT_EQ (lv->mouse_tracker () == 0, false); + EXPECT_EQ (lv->zoom_service () == 0, false); + EXPECT_EQ (lv->move_service () == 0, false); + EXPECT_EQ (lv->selection_service () == 0, false); + + lv.reset (new lay::LayoutView (0, false, 0, lay::LayoutView::LV_NoMove)); + EXPECT_EQ (lv->mouse_tracker () == 0, false); + EXPECT_EQ (lv->zoom_service () == 0, false); + EXPECT_EQ (lv->move_service () == 0, true); + EXPECT_EQ (lv->selection_service () == 0, false); + + lv.reset (new lay::LayoutView (0, false, 0, lay::LayoutView::LV_NoTracker)); + EXPECT_EQ (lv->mouse_tracker () == 0, true); + EXPECT_EQ (lv->zoom_service () == 0, false); + EXPECT_EQ (lv->move_service () == 0, false); + EXPECT_EQ (lv->selection_service () == 0, false); + + lv.reset (new lay::LayoutView (0, false, 0, lay::LayoutView::LV_NoZoom)); + EXPECT_EQ (lv->mouse_tracker () == 0, false); + EXPECT_EQ (lv->zoom_service () == 0, true); + EXPECT_EQ (lv->move_service () == 0, false); + EXPECT_EQ (lv->selection_service () == 0, false); + + lv.reset (new lay::LayoutView (0, false, 0, lay::LayoutView::LV_NoSelection)); + EXPECT_EQ (lv->mouse_tracker () == 0, false); + EXPECT_EQ (lv->zoom_service () == 0, false); + EXPECT_EQ (lv->move_service () == 0, false); + EXPECT_EQ (lv->selection_service () == 0, true); +} + #if defined(HAVE_PNG) TEST(11) { diff --git a/src/rba/unit_tests/rbaTests.cc b/src/rba/unit_tests/rbaTests.cc index 1caf18b50..b4db7f936 100644 --- a/src/rba/unit_tests/rbaTests.cc +++ b/src/rba/unit_tests/rbaTests.cc @@ -153,6 +153,7 @@ RUBYTEST (layMainWindow, "layMainWindow.rb") RUBYTEST (layMarkers, "layMarkers.rb") RUBYTEST (layMacro, "layMacro.rb") RUBYTEST (layMenuTest, "layMenuTest.rb") +RUBYTEST (layPluginTests, "layPluginTests.rb") RUBYTEST (layPixelBuffer, "layPixelBuffer.rb") RUBYTEST (laySession, "laySession.rb") RUBYTEST (laySaveLayoutOptions, "laySaveLayoutOptions.rb") diff --git a/testdata/algo/fill_tool7.gds b/testdata/algo/fill_tool7.gds new file mode 100644 index 000000000..b9beb8fde Binary files /dev/null and b/testdata/algo/fill_tool7.gds differ diff --git a/testdata/algo/fill_tool8.gds b/testdata/algo/fill_tool8.gds new file mode 100644 index 000000000..b9beb8fde Binary files /dev/null and b/testdata/algo/fill_tool8.gds differ diff --git a/testdata/algo/fill_tool9.gds b/testdata/algo/fill_tool9.gds new file mode 100644 index 000000000..06267cd02 Binary files /dev/null and b/testdata/algo/fill_tool9.gds differ diff --git a/testdata/algo/fill_tool_au7.oas b/testdata/algo/fill_tool_au7.oas new file mode 100644 index 000000000..d0bd1c1ec Binary files /dev/null and b/testdata/algo/fill_tool_au7.oas differ diff --git a/testdata/algo/fill_tool_au8.oas b/testdata/algo/fill_tool_au8.oas new file mode 100644 index 000000000..3e1e067a4 Binary files /dev/null and b/testdata/algo/fill_tool_au8.oas differ diff --git a/testdata/algo/fill_tool_au9.oas b/testdata/algo/fill_tool_au9.oas new file mode 100644 index 000000000..3454e4e7b Binary files /dev/null and b/testdata/algo/fill_tool_au9.oas differ diff --git a/testdata/drc/drcSimpleTests_47c.drc b/testdata/drc/drcSimpleTests_47c.drc new file mode 100644 index 000000000..b2b9b1f41 --- /dev/null +++ b/testdata/drc/drcSimpleTests_47c.drc @@ -0,0 +1,21 @@ + +source $drc_test_source +target $drc_test_target + +if $drc_test_deep + deep +end + +l1 = input(1, 0) +l2 = input(2, 0) + +p = fill_pattern("PAT1").shape(100, 0, box(0, 0, 5.um, 5.um)).origin(-2.5.um, -2.5.um) +l1.fill(p, hstep(10.0, 5.0), vstep(-5.0, 10.0), fill_exclude(l2)) + +p = fill_pattern("PAT1").shape(110, 0, box(0, 0, 5.um, 5.um)).origin(-2.5.um, -2.5.um) +left = l1.fill_with_left(p, hstep(10.0, 5.0), vstep(-5.0, 10.0), fill_exclude(l2)) + +l1.output(1, 0) +l2.output(2, 0) +left.output(111, 0) + diff --git a/testdata/drc/drcSimpleTests_47c.gds b/testdata/drc/drcSimpleTests_47c.gds new file mode 100644 index 000000000..5bcf8d5a7 Binary files /dev/null and b/testdata/drc/drcSimpleTests_47c.gds differ diff --git a/testdata/drc/drcSimpleTests_au47c.gds b/testdata/drc/drcSimpleTests_au47c.gds new file mode 100644 index 000000000..b90559474 Binary files /dev/null and b/testdata/drc/drcSimpleTests_au47c.gds differ diff --git a/testdata/drc/drcSimpleTests_au47cd.gds b/testdata/drc/drcSimpleTests_au47cd.gds new file mode 100644 index 000000000..bb300a375 Binary files /dev/null and b/testdata/drc/drcSimpleTests_au47cd.gds differ diff --git a/testdata/ruby/dbPolygonTest.rb b/testdata/ruby/dbPolygonTest.rb index fc039c9a7..40cabd1fd 100644 --- a/testdata/ruby/dbPolygonTest.rb +++ b/testdata/ruby/dbPolygonTest.rb @@ -1032,6 +1032,29 @@ def test_hm_decomposition end + def test_sized_transform + + # to_itype and to_dtype need to preserve the non-orientation of + # the sized() result, so they are useful for this application + + p = RBA::DPolygon::new(RBA::DBox::new(0, 0, 0.4, 0.5)) + res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-0.12, -0.22, 2).to_itype(0.001) ], false, false, 1) + res = res.collect { |p| p.to_s } + assert_equal(res, ["(120,220;120,280;280,280;280,220)"]) + res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-0.22, -0.22, 2).to_itype(0.001) ], false, false, 1) + res = res.collect { |p| p.to_s } + assert_equal(res, []) + + p = RBA::Polygon::new(RBA::Box::new(0, 0, 400, 500)) + res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-120, -220, 2).to_dtype(0.001).to_itype(0.001) ], false, false, 1) + res = res.collect { |p| p.to_s } + assert_equal(res, ["(120,220;120,280;280,280;280,220)"]) + res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-220, -220, 2).to_dtype(0.001).to_itype(0.001) ], false, false, 1) + res = res.collect { |p| p.to_s } + assert_equal(res, []) + + end + end load("test_epilogue.rb") diff --git a/testdata/ruby/layLayers.rb b/testdata/ruby/layLayers.rb index 447f3fd16..76b0f7a6b 100644 --- a/testdata/ruby/layLayers.rb +++ b/testdata/ruby/layLayers.rb @@ -234,6 +234,8 @@ def test_1a cv.insert_layer_list(1) cv.rename_layer_list(1, "x") + assert_equal(cv.layer_list_name(1), "x") + assert_equal(cv.layer_list_name(10000), "") # must not crash assert_equal(cv.current_layer_list, 1) cv.set_current_layer_list(0) assert_equal(cv.current_layer_list, 0) @@ -1036,6 +1038,45 @@ def test_7 end + # clear_layers with index and layer list with name + def test_8 + + if !RBA.constants.member?(:Application) + return + end + + app = RBA::Application.instance + mw = app.main_window + mw.close_all + + mw.load_layout( ENV["TESTSRC"] + "/testdata/gds/t11.gds", 1 ) + + cv = mw.current_view + + cv.clear_layers + assert_equal(lnodes_str("", cv.begin_layers(0)), "") + + cv.rename_layer_list(0, "x") + assert_equal(cv.layer_list_name(0), "x") + + cv.insert_layer(0, cv.end_layers(0), RBA::LayerProperties::new) + assert_equal(lnodes_str("", cv.begin_layers(0)), "*/*@*\n") + + cv.clear_layers(0) + assert_equal(lnodes_str("", cv.begin_layers(0)), "") + assert_equal(cv.layer_list_name(0), "x") + + cv.rename_layer_list(cv.current_layer_list, "y") + assert_equal(cv.layer_list_name(0), "y") + cv.insert_layer(cv.end_layers, RBA::LayerProperties::new) + assert_equal(lnodes_str("", cv.begin_layers), "*/*@*\n") + + cv.clear_layers + assert_equal(lnodes_str("", cv.begin_layers), "") + assert_equal(cv.layer_list_name(cv.current_layer_list), "y") + + end + end load("test_epilogue.rb") diff --git a/testdata/ruby/layLayoutView.rb b/testdata/ruby/layLayoutView.rb index 2d55f84e1..e56c64a05 100644 --- a/testdata/ruby/layLayoutView.rb +++ b/testdata/ruby/layLayoutView.rb @@ -506,9 +506,8 @@ def test_5 end class DummyPlugin < RBA::Plugin - def initialize(manager, view) - self.manager = manager - self.view = view + def initialize + super end end @@ -516,8 +515,8 @@ class DummyPluginFactory < RBA::PluginFactory def initialize() register(1000, "dummy_plugin", "Dummy Plugin") end - def create_plugin(manager, unused, view) - DummyPlugin::new(manager, view) + def create_plugin(manager, dispatcher, view) + DummyPlugin::new end end @@ -601,6 +600,28 @@ def test_9 end + # private config + def test_10 + + lv = RBA::LayoutView::new(true) + + assert_equal(lv.get_config_names.member?("edit-grid"), true) + lv.set_config("edit-grid", "0.01") + # smoke test + lv.commit_config + assert_equal(lv.get_config("edit-grid"), "0.01") + lv.clear_config + assert_equal(lv.get_config("edit-grid"), "") + + # unknown config names can be used, but are not initialized + lv.set_config("does-not-exist", "aaa") + assert_equal(lv.get_config_names.member?("does-not-exist"), true) + assert_equal(lv.get_config("does-not-exist"), "aaa") + lv.clear_config + assert_equal(lv.get_config_names.member?("does-not-exist"), false) + + end + end load("test_epilogue.rb") diff --git a/testdata/ruby/layPluginTests.rb b/testdata/ruby/layPluginTests.rb new file mode 100644 index 000000000..245a91e1e --- /dev/null +++ b/testdata/ruby/layPluginTests.rb @@ -0,0 +1,189 @@ +# encoding: UTF-8 + +# KLayout Layout Viewer +# Copyright (C) 2006-2025 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 + +if !$:.member?(File::dirname($0)) + $:.push(File::dirname($0)) +end + +load("test_prologue.rb") + +class PluginFactory < RBA::PluginFactory + def initialize() + register(1000, "plugin_for_test", "Plugin") + @pi = nil + end + def create_plugin(manager, unused, view) + @pi = RBA::Plugin::new + @pi + end + def pi + @pi + end +end + +has_qt = RBA.constants.member?(:EditorOptionsPage) && RBA.constants.member?(:MainWindow) +if has_qt + + class Plugin2EditorOptionsPage < RBA::EditorOptionsPage + def initialize + super("title", 1) + end + end + + class Plugin2ConfigPage < RBA::ConfigPage + def initialize + super("title") + end + end + + class Plugin2 < RBA::Plugin + def set_tp(tp) + @tp = tp + end + def has_tracking_position + !!@tp + end + def tracking_position + @tp || RBA::DPoint::new + end + end + + class PluginFactory2 < RBA::PluginFactory + def initialize() + @ep = 0 + @cp = 0 + @pi = nil + register(1001, "plugin_for_test2", "Plugin2") + end + def create_plugin(manager, unused, view) + @pi = Plugin2::new + @pi + end + def create_editor_options_pages + add_editor_options_page(Plugin2EditorOptionsPage::new) + @ep += 1 + end + def create_config_pages + add_config_page(Plugin2ConfigPage::new) + @cp += 1 + end + def pi + @pi + end + def ep + @ep + end + def cp + @cp + end + end + + class LayPlugin_TestClass < TestBase + + def test_1 + + assert_equal(RBA::Plugin::AC_Global.to_s, "AC_Global") + assert_equal(RBA::Plugin::AC_Any.to_s, "AC_Any") + assert_equal(RBA::Plugin::AC_Diagonal.to_s, "AC_Diagonal") + assert_equal(RBA::Plugin::AC_Horizontal.to_s, "AC_Horizontal") + assert_equal(RBA::Plugin::AC_Vertical.to_s, "AC_Vertical") + + assert_equal(RBA::Plugin::ac_from_buttons(0), RBA::Plugin::AC_Global) + assert_equal(RBA::Plugin::ac_from_buttons(1), RBA::Plugin::AC_Ortho) + assert_equal(RBA::Plugin::ac_from_buttons(2), RBA::Plugin::AC_Diagonal) + + begin + + dpi = PluginFactory::new + dpi2 = PluginFactory2::new + + # Create a new layout + main_window = RBA::MainWindow.instance() + main_window.close_all + main_window.create_layout(2) + + pi = dpi.pi + pi2 = dpi2.pi + + # smoke test + pi.grab_mouse + pi.ungrab_mouse + pi.set_cursor(RBA::Cursor::Wait) + pi.add_edge_marker(RBA::DEdge::new) + pi.add_mouse_cursor(RBA::DPoint::new) + pi.clear_mouse_cursors + + # virtual methods + assert_equal(pi.has_tracking_position_test, false) + pi.clear_mouse_cursors + pi.add_mouse_cursor(RBA::DPoint::new(1, 2)) + assert_equal(pi.has_tracking_position_test, true) + assert_equal(pi.tracking_position_test.to_s, "1,2") + pi.clear_mouse_cursors + assert_equal(pi.has_tracking_position_test, false) + + assert_equal(pi2.has_tracking_position_test, false) + pi2.set_tp(RBA::DPoint::new(2, 3)) + assert_equal(pi2.has_tracking_position_test, true) + assert_equal(pi2.tracking_position_test.to_s, "2,3") + pi2.set_tp(nil) + assert_equal(pi2.has_tracking_position_test, false) + + assert_equal(dpi2.ep, 1) + assert_equal(dpi2.cp, 1) + assert_equal(pi2.editor_options_pages.size, 1) + assert_equal(pi2.editor_options_pages[0].class.to_s, "Plugin2EditorOptionsPage") + + pi.configure_test("edit-grid", "0.0") + assert_equal(pi.snap(RBA::DPoint::new(0.01, 0.02)).to_s, "0.01,0.02") + assert_equal(pi.snap(RBA::DVector::new(0.01, 0.02)).to_s, "0.01,0.02") + pi.configure_test("edit-grid", "0.1") + assert_equal(pi.snap(RBA::DPoint::new(0.11, 0.18)).to_s, "0.1,0.2") + assert_equal(pi.snap(RBA::DVector::new(0.11, 0.18)).to_s, "0.1,0.2") + + pi.configure_test("edit-connect-angle-mode", "ortho") + assert_equal(pi.snap(RBA::DPoint::new(1.5, 2.1), RBA::DPoint::new(1, 2), true).to_s, "1.5,2") + assert_equal(pi.snap(RBA::DPoint::new(1.5, 2.1), RBA::DPoint::new(1, 2), false).to_s, "1.5,2.1") + assert_equal(pi.snap(RBA::DPoint::new(1.5, 2.1), RBA::DPoint::new(1, 2), false, RBA::Plugin::AC_Ortho).to_s, "1.5,2") + + pi.configure_test("edit-connect-angle-mode", "ortho") + assert_equal(pi.snap(RBA::DVector::new(0.5, 2.1), true).to_s, "0,2.1") + assert_equal(pi.snap(RBA::DVector::new(0.5, 2.1), false).to_s, "0.5,2.1") + assert_equal(pi.snap(RBA::DVector::new(0.5, 2.1), false, RBA::Plugin::AC_Ortho).to_s, "0,2.1") + + ensure + main_window.close_all + dpi._destroy + dpi2._destroy + end + + end + + end + +else + + # at least one test is needed + class LayPlugin_TestClass < TestBase + end + +end + +load("test_epilogue.rb") +