From b8263d25296caeddb0ac80c5a5ac78d2cdaec23f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 22 Sep 2022 21:17:51 +0200 Subject: [PATCH 01/54] Fixed #1159 (valgrind error) --- src/tl/tl/tlString.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tl/tl/tlString.cc b/src/tl/tl/tlString.cc index 178020c61..bb2a14dad 100644 --- a/src/tl/tl/tlString.cc +++ b/src/tl/tl/tlString.cc @@ -256,7 +256,7 @@ std::string to_lower_case (const std::string &s) std::string to_local (const std::string &s) { - std::unique_ptr buffer (new char [MB_CUR_MAX]); // MB_CUR_MAX isn't a constant + std::unique_ptr buffer (new char [MB_CUR_MAX]); // MB_CUR_MAX isn't a constant std::string ls; std::wstring ws = to_wstring (s); From 4236360620bf64eb5189a3183af6e734ea8c7135 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 23 Sep 2022 22:00:33 +0200 Subject: [PATCH 02/54] Added doc and icon to dependencies. --- src/klayout.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/klayout.pro b/src/klayout.pro index 897d5b516..8c29c0b11 100644 --- a/src/klayout.pro +++ b/src/klayout.pro @@ -112,7 +112,7 @@ equals(HAVE_RUBY, "1") { plugins.depends += lay - klayout_main.depends += plugins $$MAIN_DEPENDS + klayout_main.depends += doc icons plugins $$MAIN_DEPENDS } else { From 293f26ddc0bce77f959f6cd13ad792148f97445e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 Sep 2022 00:35:59 +0200 Subject: [PATCH 03/54] WIP: multiple tech stacks for net tracer, first steps --- .../net_tracer/db_plugin/dbNetTracerIO.cc | 46 +- .../net_tracer/db_plugin/dbNetTracerIO.h | 104 +++- .../net_tracer/db_plugin/dbNetTracerPlugin.cc | 41 +- .../db_plugin/gsiDeclDbNetTracer.cc | 95 +++- .../NetTracerTechComponentEditor.ui | 450 +++++++----------- ...O.cc => layNetTracerConnectivityEditor.cc} | 66 ++- ...rIO.h => layNetTracerConnectivityEditor.h} | 24 +- .../lay_plugin/layNetTracerDialog.cc | 2 +- .../lay_plugin/layNetTracerPlugin.cc | 2 +- .../net_tracer/lay_plugin/lay_plugin.pro | 7 +- .../net_tracer/unit_tests/dbNetTracerTests.cc | 46 +- .../net_tracer/unit_tests/dbTraceAllNets.cc | 26 +- 12 files changed, 495 insertions(+), 414 deletions(-) rename src/plugins/tools/net_tracer/lay_plugin/{layNetTracerIO.cc => layNetTracerConnectivityEditor.cc} (92%) rename src/plugins/tools/net_tracer/lay_plugin/{layNetTracerIO.h => layNetTracerConnectivityEditor.h} (75%) diff --git a/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.cc b/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.cc index 2da4268f5..8aa4697b9 100644 --- a/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.cc +++ b/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.cc @@ -182,9 +182,9 @@ NetTracerLayerExpressionInfo::compile (const std::string &s) } NetTracerLayerExpression * -NetTracerLayerExpressionInfo::get_expr (const db::LayerProperties &lp, const db::Layout &layout, const NetTracerTechnologyComponent &tech, const std::set &used_symbols) const +NetTracerLayerExpressionInfo::get_expr (const db::LayerProperties &lp, const db::Layout &layout, const NetTracerConnectivity &tech, const std::set &used_symbols) const { - for (NetTracerTechnologyComponent::const_symbol_iterator s = tech.begin_symbols (); s != tech.end_symbols (); ++s) { + for (NetTracerConnectivity::const_symbol_iterator s = tech.begin_symbols (); s != tech.end_symbols (); ++s) { if (s->symbol ().log_equal (lp)) { std::set us = used_symbols; if (! us.insert (s->symbol ().to_string ()).second) { @@ -204,14 +204,14 @@ NetTracerLayerExpressionInfo::get_expr (const db::LayerProperties &lp, const db: } NetTracerLayerExpression * -NetTracerLayerExpressionInfo::get (const db::Layout &layout, const NetTracerTechnologyComponent &tech) const +NetTracerLayerExpressionInfo::get (const db::Layout &layout, const NetTracerConnectivity &tech) const { std::set us; return get (layout, tech, us); } NetTracerLayerExpression * -NetTracerLayerExpressionInfo::get (const db::Layout &layout, const NetTracerTechnologyComponent &tech, const std::set &used_symbols) const +NetTracerLayerExpressionInfo::get (const db::Layout &layout, const NetTracerConnectivity &tech, const std::set &used_symbols) const { NetTracerLayerExpression *e = 0; @@ -252,7 +252,7 @@ NetTracerConnectionInfo::NetTracerConnectionInfo (const NetTracerLayerExpression // .. nothing yet .. } -static int get_layer_id (const NetTracerLayerExpressionInfo &e, const db::Layout &layout, const NetTracerTechnologyComponent &tech, NetTracerData *data) +static int get_layer_id (const NetTracerLayerExpressionInfo &e, const db::Layout &layout, const NetTracerConnectivity &tech, NetTracerData *data) { std::unique_ptr expr_in (NetTracerLayerExpressionInfo::compile (e.to_string ()).get (layout, tech)); int l = expr_in->alias_for (); @@ -266,7 +266,7 @@ static int get_layer_id (const NetTracerLayerExpressionInfo &e, const db::Layout } NetTracerConnection -NetTracerConnectionInfo::get (const db::Layout &layout, const NetTracerTechnologyComponent &tech, NetTracerData &data) const +NetTracerConnectionInfo::get (const db::Layout &layout, const NetTracerConnectivity &tech, NetTracerData &data) const { int la = get_layer_id (m_la, layout, tech, &data); int lb = get_layer_id (m_lb, layout, tech, &data); @@ -490,30 +490,40 @@ NetTracerNet::define_layer (unsigned int l, const db::LayerProperties &lp, const NetTracerTechnologyComponent::NetTracerTechnologyComponent () : db::TechnologyComponent (net_tracer_component_name (), tl::to_string (tr ("Connectivity"))) +{ + // .. nothing yet .. +} + +// ----------------------------------------------------------------------------------- +// NetTracerConnectivity implementation + +NetTracerConnectivity::NetTracerConnectivity () { // .. nothing yet .. } -NetTracerTechnologyComponent::NetTracerTechnologyComponent (const NetTracerTechnologyComponent &d) - : db::TechnologyComponent (net_tracer_component_name (), tl::to_string (tr ("Connectivity"))) +NetTracerConnectivity::NetTracerConnectivity (const NetTracerConnectivity &d) { - m_connections = d.m_connections; - m_symbols = d.m_symbols; + operator= (d); } -NetTracerTechnologyComponent &NetTracerTechnologyComponent::operator= (const NetTracerTechnologyComponent &d) +NetTracerConnectivity &NetTracerConnectivity::operator= (const NetTracerConnectivity &d) { - m_connections = d.m_connections; - m_symbols = d.m_symbols; + if (this != &d) { + m_connections = d.m_connections; + m_symbols = d.m_symbols; + m_name = d.m_name; + m_description = d.m_description; + } return *this; } NetTracerData -NetTracerTechnologyComponent::get_tracer_data (const db::Layout &layout) const +NetTracerConnectivity::get_tracer_data (const db::Layout &layout) const { // test run on the expressions to verify their syntax int n = 1; - for (NetTracerTechnologyComponent::const_iterator c = begin (); c != end (); ++c, ++n) { + for (NetTracerConnectivity::const_iterator c = begin (); c != end (); ++c, ++n) { if (c->layer_a ().to_string ().empty ()) { throw tl::Exception (tl::to_string (tr ("Missing first layer specification on connectivity specification #%d")), n); } @@ -523,7 +533,7 @@ NetTracerTechnologyComponent::get_tracer_data (const db::Layout &layout) const } n = 1; - for (NetTracerTechnologyComponent::const_symbol_iterator s = begin_symbols (); s != end_symbols (); ++s, ++n) { + for (NetTracerConnectivity::const_symbol_iterator s = begin_symbols (); s != end_symbols (); ++s, ++n) { if (s->symbol ().to_string ().empty ()) { throw tl::Exception (tl::to_string (tr ("Missing symbol name on symbol specification #%d")), n); } @@ -540,12 +550,12 @@ NetTracerTechnologyComponent::get_tracer_data (const db::Layout &layout) const NetTracerData data; // register a logical layer for each original one as alias and one for each expression with a new ID - for (db::NetTracerTechnologyComponent::const_symbol_iterator s = begin_symbols (); s != end_symbols (); ++s) { + for (db::NetTracerConnectivity::const_symbol_iterator s = begin_symbols (); s != end_symbols (); ++s) { db::NetTracerLayerExpression *expr = db::NetTracerLayerExpressionInfo::compile (s->expression ()).get (layout, *this); data.register_logical_layer (expr, s->symbol ().to_string ().c_str ()); } - for (db::NetTracerTechnologyComponent::const_iterator c = begin (); c != end (); ++c) { + for (db::NetTracerConnectivity::const_iterator c = begin (); c != end (); ++c) { data.add_connection (c->get (layout, *this, data)); } diff --git a/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.h b/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.h index b0f760256..82dedb2c1 100644 --- a/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.h +++ b/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.h @@ -32,7 +32,7 @@ namespace db { -class NetTracerTechnologyComponent; +class NetTracerConnectivity; DB_PLUGIN_PUBLIC std::string net_tracer_component_name (); @@ -57,7 +57,7 @@ public: return m_expression; } - NetTracerLayerExpression *get (const db::Layout &layout, const NetTracerTechnologyComponent &tech) const; + NetTracerLayerExpression *get (const db::Layout &layout, const NetTracerConnectivity &tech) const; private: std::string m_expression; @@ -70,8 +70,8 @@ private: static NetTracerLayerExpressionInfo parse_mult (tl::Extractor &ex); static NetTracerLayerExpressionInfo parse_atomic (tl::Extractor &ex); - NetTracerLayerExpression *get (const db::Layout &layout, const NetTracerTechnologyComponent &tech, const std::set &used_symbols) const; - NetTracerLayerExpression *get_expr (const db::LayerProperties &lp, const db::Layout &layout, const NetTracerTechnologyComponent &tech, const std::set &used_symbols) const; + NetTracerLayerExpression *get (const db::Layout &layout, const NetTracerConnectivity &tech, const std::set &used_symbols) const; + NetTracerLayerExpression *get_expr (const db::LayerProperties &lp, const db::Layout &layout, const NetTracerConnectivity &tech, const std::set &used_symbols) const; }; class DB_PLUGIN_PUBLIC NetTracerConnectionInfo @@ -81,7 +81,7 @@ public: NetTracerConnectionInfo (const NetTracerLayerExpressionInfo &la, const NetTracerLayerExpressionInfo &lb); NetTracerConnectionInfo (const NetTracerLayerExpressionInfo &la, const NetTracerLayerExpressionInfo &via, const NetTracerLayerExpressionInfo &lb); - NetTracerConnection get (const db::Layout &layout, const NetTracerTechnologyComponent &tech, NetTracerData &data) const; + NetTracerConnection get (const db::Layout &layout, const NetTracerConnectivity &tech, NetTracerData &data) const; std::string to_string () const; void parse (tl::Extractor &ex); @@ -357,8 +357,7 @@ private: void define_layer (unsigned int l, const db::LayerProperties &lp, const db::LayerProperties &lp_representative); }; -class DB_PLUGIN_PUBLIC NetTracerTechnologyComponent - : public db::TechnologyComponent +class DB_PLUGIN_PUBLIC NetTracerConnectivity { public: typedef std::vector::const_iterator const_iterator; @@ -366,9 +365,29 @@ public: typedef std::vector::const_iterator const_symbol_iterator; typedef std::vector::iterator symbol_iterator; - NetTracerTechnologyComponent (); - NetTracerTechnologyComponent (const NetTracerTechnologyComponent &d); - NetTracerTechnologyComponent &operator= (const NetTracerTechnologyComponent &d); + NetTracerConnectivity (); + NetTracerConnectivity (const NetTracerConnectivity &d); + NetTracerConnectivity &operator= (const NetTracerConnectivity &d); + + const std::string &name () const + { + return m_name; + } + + void set_name (const std::string &n) + { + m_name = n; + } + + const std::string &description () const + { + return m_description; + } + + void set_description (const std::string &d) + { + m_description = d; + } const_iterator begin () const { @@ -458,14 +477,73 @@ public: NetTracerData get_tracer_data (const db::Layout &layout) const; - db::TechnologyComponent *clone () const +private: + std::vector m_connections; + std::vector m_symbols; + std::string m_name, m_description; +}; + +class DB_PLUGIN_PUBLIC NetTracerTechnologyComponent + : public db::TechnologyComponent +{ +public: + typedef std::vector::const_iterator const_iterator; + typedef std::vector::iterator iterator; + + NetTracerTechnologyComponent (); + + size_t size () const + { + return m_connectivity.size (); + } + + void push_back (const db::NetTracerConnectivity &c) + { + m_connectivity.push_back (c); + } + + void clear () + { + m_connectivity.clear (); + } + + void erase (iterator i) + { + m_connectivity.erase (i); + } + + void insert (iterator i, const db::NetTracerConnectivity &c) + { + m_connectivity.insert (i, c); + } + + const_iterator begin () const + { + return m_connectivity.begin (); + } + + const_iterator end () const + { + return m_connectivity.begin (); + } + + iterator begin () + { + return m_connectivity.begin (); + } + + iterator end () + { + return m_connectivity.begin (); + } + + db::NetTracerTechnologyComponent *clone () const { return new NetTracerTechnologyComponent (*this); } private: - std::vector m_connections; - std::vector m_symbols; + std::vector m_connectivity; }; } diff --git a/src/plugins/tools/net_tracer/db_plugin/dbNetTracerPlugin.cc b/src/plugins/tools/net_tracer/db_plugin/dbNetTracerPlugin.cc index d0793d4be..4c5632c23 100644 --- a/src/plugins/tools/net_tracer/db_plugin/dbNetTracerPlugin.cc +++ b/src/plugins/tools/net_tracer/db_plugin/dbNetTracerPlugin.cc @@ -65,6 +65,33 @@ namespace tl }; } +namespace +{ + +template +struct FallbackXMLWriteAdapator +{ + FallbackXMLWriteAdapator (void (db::NetTracerConnectivity::*member) (const Value &)) + : mp_member (member) + { + // .. nothing yet .. + } + + void operator () (db::NetTracerTechnologyComponent &owner, tl::XMLReaderState &reader) const + { + if (owner.size () == 0) { + owner.push_back (db::NetTracerConnectivity ()); + } + tl::XMLObjTag tag; + ((*owner.begin ()).*mp_member) (*reader.back (tag)); + } + +private: + void (db::NetTracerConnectivity::*mp_member) (const Value &); +}; + +} + namespace db { @@ -86,8 +113,18 @@ public: virtual tl::XMLElementBase *xml_element () const { return new db::TechnologyComponentXMLElement (net_tracer_component_name (), - tl::make_member ((NetTracerTechnologyComponent::const_iterator (NetTracerTechnologyComponent::*) () const) &NetTracerTechnologyComponent::begin, (NetTracerTechnologyComponent::const_iterator (NetTracerTechnologyComponent::*) () const) &NetTracerTechnologyComponent::end, &NetTracerTechnologyComponent::add, "connection") + - tl::make_member ((NetTracerTechnologyComponent::const_symbol_iterator (NetTracerTechnologyComponent::*) () const) &NetTracerTechnologyComponent::begin_symbols, (NetTracerTechnologyComponent::const_symbol_iterator (NetTracerTechnologyComponent::*) () const) &NetTracerTechnologyComponent::end_symbols, &NetTracerTechnologyComponent::add_symbol, "symbols") + // Fallback readers for migrating pre-0.28 setups to 0.28 + tl::XMLMember, FallbackXMLWriteAdapator , tl::XMLStdConverter > ( + tl::XMLMemberDummyReadAdaptor (), + FallbackXMLWriteAdapator (&NetTracerConnectivity::add), "connection") + + tl::XMLMember, FallbackXMLWriteAdapator , tl::XMLStdConverter > ( + tl::XMLMemberDummyReadAdaptor (), + FallbackXMLWriteAdapator (&NetTracerConnectivity::add_symbol), "symbols") + + // 0.28 definitions + tl::make_element ((NetTracerTechnologyComponent::const_iterator (NetTracerTechnologyComponent::*) () const) &NetTracerTechnologyComponent::begin, (NetTracerTechnologyComponent::const_iterator (NetTracerTechnologyComponent::*) () const) &NetTracerTechnologyComponent::end, (void (NetTracerTechnologyComponent::*) (const NetTracerConnectivity &)) &NetTracerTechnologyComponent::push_back, "connectivity", + tl::make_member ((NetTracerConnectivity::const_iterator (NetTracerConnectivity::*) () const) &NetTracerConnectivity::begin, (NetTracerConnectivity::const_iterator (NetTracerConnectivity::*) () const) &NetTracerConnectivity::end, &NetTracerConnectivity::add, "connection") + + tl::make_member ((NetTracerConnectivity::const_symbol_iterator (NetTracerConnectivity::*) () const) &NetTracerConnectivity::begin_symbols, (NetTracerConnectivity::const_symbol_iterator (NetTracerConnectivity::*) () const) &NetTracerConnectivity::end_symbols, &NetTracerConnectivity::add_symbol, "symbols") + ) ); } }; diff --git a/src/plugins/tools/net_tracer/db_plugin/gsiDeclDbNetTracer.cc b/src/plugins/tools/net_tracer/db_plugin/gsiDeclDbNetTracer.cc index ff1f6b5a0..e14be4dcf 100644 --- a/src/plugins/tools/net_tracer/db_plugin/gsiDeclDbNetTracer.cc +++ b/src/plugins/tools/net_tracer/db_plugin/gsiDeclDbNetTracer.cc @@ -32,14 +32,14 @@ namespace gsi // ----------------------------------------------------------------------------------- // GSI binding -static void def_connection2 (db::NetTracerTechnologyComponent *tech, const std::string &la, const std::string &lb) +static void def_connection2 (db::NetTracerConnectivity *tech, const std::string &la, const std::string &lb) { db::NetTracerLayerExpressionInfo la_info = db::NetTracerLayerExpressionInfo::compile (la); db::NetTracerLayerExpressionInfo lb_info = db::NetTracerLayerExpressionInfo::compile (lb); tech->add (db::NetTracerConnectionInfo (la_info, lb_info)); } -static void def_connection3 (db::NetTracerTechnologyComponent *tech, const std::string &la, const std::string &via, const std::string &lb) +static void def_connection3 (db::NetTracerConnectivity *tech, const std::string &la, const std::string &via, const std::string &lb) { db::NetTracerLayerExpressionInfo la_info = db::NetTracerLayerExpressionInfo::compile (la); db::NetTracerLayerExpressionInfo via_info = db::NetTracerLayerExpressionInfo::compile (via); @@ -47,14 +47,26 @@ static void def_connection3 (db::NetTracerTechnologyComponent *tech, const std:: tech->add (db::NetTracerConnectionInfo (la_info, via_info, lb_info)); } -static void def_symbol (db::NetTracerTechnologyComponent *tech, const std::string &name, const std::string &expr) +static void def_symbol (db::NetTracerConnectivity *tech, const std::string &name, const std::string &expr) { tech->add_symbol (db::NetTracerSymbolInfo (db::LayerProperties (name), expr)); } -gsi::Class &decl_dbTechnologyComponent (); - -gsi::Class decl_NetTracerTechnology (decl_dbTechnologyComponent (), "db", "NetTracerTechnology", +gsi::Class decl_NetTracerConnectivity ("db", "NetTracerConnectivity", + gsi::method ("name", &db::NetTracerConnectivity::name, + "@brief Gets the name of the connectivty definition\n" + "The name is an optional string defining the formal name for this definition.\n" + ) + + gsi::method ("name=", &db::NetTracerConnectivity::set_name, gsi::arg ("n"), + "@brief Sets the name of the connectivty definition\n" + ) + + gsi::method ("description", &db::NetTracerConnectivity::description, + "@brief Gets the description text of the connectivty definition\n" + "The description is an optional string giving a human-readable description for this definition." + ) + + gsi::method ("description=", &db::NetTracerConnectivity::set_description, gsi::arg ("d"), + "@brief Sets the description of the connectivty definition\n" + ) + gsi::method_ext ("connection", &def_connection2, gsi::arg("a"), gsi::arg("b"), "@brief Defines a connection between two materials\n" "See the class description for details about this method." @@ -68,7 +80,7 @@ gsi::Class decl_NetTracerTechnology (decl_dbTe "Defines a sub-expression to be used in further symbols or material expressions. " "For the detailed notation of the expression see the description of the net tracer feature." ), - "@brief A technology description for the net tracer\n" + "@brief A connectivity description for the net tracer\n" "\n" "This object represents the technology description for the net tracer (represented by the \\NetTracer class).\n" "A technology description basically consists of connection declarations.\n" @@ -83,16 +95,17 @@ gsi::Class decl_NetTracerTechnology (decl_dbTe "\n" "For details about the expressions see the description of the net tracer feature.\n" "\n" - "This class has been introduced in version 0.25.\n" + "This class has been introduced in version 0.28 and replaces the 'NetTracerTechnology' class which " + "has been generalized.\n" ); -static void trace1 (db::NetTracer *net_tracer, const db::NetTracerTechnologyComponent &tech, const db::Layout &layout, const db::Cell &cell, const db::Point &start_point, unsigned int start_layer) +static void trace1 (db::NetTracer *net_tracer, const db::NetTracerConnectivity &tech, const db::Layout &layout, const db::Cell &cell, const db::Point &start_point, unsigned int start_layer) { db::NetTracerData tracer_data = tech.get_tracer_data (layout); net_tracer->trace (layout, cell, start_point, start_layer, tracer_data); } -static void trace2 (db::NetTracer *net_tracer, const db::NetTracerTechnologyComponent &tech, const db::Layout &layout, const db::Cell &cell, const db::Point &start_point, unsigned int start_layer, const db::Point &stop_point, unsigned int stop_layer) +static void trace2 (db::NetTracer *net_tracer, const db::NetTracerConnectivity &tech, const db::Layout &layout, const db::Cell &cell, const db::Point &start_point, unsigned int start_layer, const db::Point &stop_point, unsigned int stop_layer) { db::NetTracerData tracer_data = tech.get_tracer_data (layout); net_tracer->trace (layout, cell, start_point, start_layer, stop_point, stop_layer, tracer_data); @@ -106,7 +119,31 @@ static db::NetTracerData get_tracer_data_from_tech (const std::string &tech_name const db::NetTracerTechnologyComponent *tech_component = dynamic_cast (tech->component_by_name (db::net_tracer_component_name ())); tl_assert (tech_component != 0); - return tech_component->get_tracer_data (layout); + if (tech_component->size () < 1) { + throw tl::Exception (tl::to_string (tr ("No connectivity setup exists for technology '%s'")), tech_name); + } + if (tech_component->size () > 1) { + throw tl::Exception (tl::to_string (tr ("Multiple connectivity setups exist for technology '%s' - specify a name")), tech_name); + } + + return tech_component->begin ()->get_tracer_data (layout); +} + +static db::NetTracerData get_tracer_data_from_tech (const std::string &tech_name, const std::string &name, const db::Layout &layout) +{ + const db::Technology *tech = db::Technologies::instance ()->technology_by_name (tech_name); + tl_assert (tech != 0); + + const db::NetTracerTechnologyComponent *tech_component = dynamic_cast (tech->component_by_name (db::net_tracer_component_name ())); + tl_assert (tech_component != 0); + + for (auto t = tech_component->begin (); t != tech_component->end (); ++t) { + if (t->name () == name) { + return t->get_tracer_data (layout); + } + } + + throw tl::Exception (tl::to_string (tr ("No connectivity setup exists with name '%s' for technology '%s'")), name, tech_name); } static void trace1_tn (db::NetTracer *net_tracer, const std::string &tech, const db::Layout &layout, const db::Cell &cell, const db::Point &start_point, unsigned int start_layer) @@ -115,12 +152,24 @@ static void trace1_tn (db::NetTracer *net_tracer, const std::string &tech, const net_tracer->trace (layout, cell, start_point, start_layer, tracer_data); } +static void trace1_tn2 (db::NetTracer *net_tracer, const std::string &tech, const std::string &name, const db::Layout &layout, const db::Cell &cell, const db::Point &start_point, unsigned int start_layer) +{ + db::NetTracerData tracer_data = get_tracer_data_from_tech (tech, name, layout); + net_tracer->trace (layout, cell, start_point, start_layer, tracer_data); +} + static void trace2_tn (db::NetTracer *net_tracer, const std::string &tech, const db::Layout &layout, const db::Cell &cell, const db::Point &start_point, unsigned int start_layer, const db::Point &stop_point, unsigned int stop_layer) { db::NetTracerData tracer_data = get_tracer_data_from_tech (tech, layout); net_tracer->trace (layout, cell, start_point, start_layer, stop_point, stop_layer, tracer_data); } +static void trace2_tn2 (db::NetTracer *net_tracer, const std::string &tech, const std::string &name, const db::Layout &layout, const db::Cell &cell, const db::Point &start_point, unsigned int start_layer, const db::Point &stop_point, unsigned int stop_layer) +{ + db::NetTracerData tracer_data = get_tracer_data_from_tech (tech, name, layout); + net_tracer->trace (layout, cell, start_point, start_layer, stop_point, stop_layer, tracer_data); +} + gsi::Class decl_NetElement ("db", "NetElement", gsi::method ("trans", &db::NetTracerShape::trans, "@brief Gets the transformation to apply for rendering the shape in the original top cell\n" @@ -178,7 +227,7 @@ gsi::Class decl_NetTracer ("db", "NetTracer", "A path extraction version is provided as well which will extract one (the presumably shortest) path between two " "points.\n" "\n" - "@param tech The technology definition\n" + "@param tech The connectivity definition\n" "@param layout The layout on which to run the extraction\n" "@param cell The cell on which to run the extraction (child cells will be included)\n" "@param start_point The start point from which to start extraction of the net\n" @@ -194,7 +243,7 @@ gsi::Class decl_NetTracer ("db", "NetTracer", "\n" "This version runs a path extraction and will deliver elements forming one path leading from the start to the end point.\n" "\n" - "@param tech The technology definition\n" + "@param tech The connectivity definition\n" "@param layout The layout on which to run the extraction\n" "@param cell The cell on which to run the extraction (child cells will be included)\n" "@param start_point The start point from which to start extraction of the net\n" @@ -205,13 +254,31 @@ gsi::Class decl_NetTracer ("db", "NetTracer", gsi::method_ext ("trace", &trace1_tn, gsi::arg ("tech"), gsi::arg ("layout"), gsi::arg ("cell"), gsi::arg ("start_point"), gsi::arg ("start_layer"), "@brief Runs a net extraction taking a predefined technology\n" "This method behaves identical as the version with a technology object, except that it will look for a technology " - "with the given name to obtain the extraction setup." + "with the given name to obtain the extraction setup.\n" + "The technology is looked up by technology name. A version of this method exists where it is possible " + "to specify the name of the particular connectivity to use in case there are multiple definitions available." + ) + + gsi::method_ext ("trace", &trace1_tn2, gsi::arg ("tech"), gsi::arg ("connectivity_name"), gsi::arg ("layout"), gsi::arg ("cell"), gsi::arg ("start_point"), gsi::arg ("start_layer"), + "@brief Runs a net extraction taking a predefined technology\n" + "This method behaves identical as the version with a technology object, except that it will look for a technology " + "with the given name to obtain the extraction setup. " + "This version allows specifying the name of the connecvitiy setup.\n" + "\n" + "This method variant has been introduced in version 0.28." ) + gsi::method_ext ("trace", &trace2_tn, gsi::arg ("tech"), gsi::arg ("layout"), gsi::arg ("cell"), gsi::arg ("start_point"), gsi::arg ("start_layer"), gsi::arg ("stop_point"), gsi::arg ("stop_layer"), "@brief Runs a path extraction taking a predefined technology\n" "This method behaves identical as the version with a technology object, except that it will look for a technology " "with the given name to obtain the extraction setup." ) + + gsi::method_ext ("trace", &trace2_tn2, gsi::arg ("tech"), gsi::arg ("connectivity_name"), gsi::arg ("layout"), gsi::arg ("cell"), gsi::arg ("start_point"), gsi::arg ("start_layer"), gsi::arg ("stop_point"), gsi::arg ("stop_layer"), + "@brief Runs a path extraction taking a predefined technology\n" + "This method behaves identical as the version with a technology object, except that it will look for a technology " + "with the given name to obtain the extraction setup." + "This version allows specifying the name of the connecvitiy setup.\n" + "\n" + "This method variant has been introduced in version 0.28." + ) + gsi::iterator ("each_element", &db::NetTracer::begin, &db::NetTracer::end, "@brief Iterates over the elements found during extraction\n" "The elements are available only after the extraction has been performed." diff --git a/src/plugins/tools/net_tracer/lay_plugin/NetTracerTechComponentEditor.ui b/src/plugins/tools/net_tracer/lay_plugin/NetTracerTechComponentEditor.ui index 188969569..8ee37634a 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/NetTracerTechComponentEditor.ui +++ b/src/plugins/tools/net_tracer/lay_plugin/NetTracerTechComponentEditor.ui @@ -1,7 +1,8 @@ - + + NetTracerTechComponentEditor - - + + 0 0 @@ -9,324 +10,221 @@ 449 - + Form - - - 9 - - - 6 - + - - - Qt::Vertical + + + Qt::Horizontal - - + + QFrame::NoFrame - + QFrame::Raised - - + + 0 - - 6 + + 0 - - - - QFrame::NoFrame + + 0 + + + 0 + + + + + ... - - QFrame::Raised + + + :/down_16px.png:/down_16px.png - - - 0 - - - 6 - - - - - <html>Connectivity (<a href="int:/about/connectivity.xml">See here for details</a>)</html> - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - + + + + ... + + + + :/del_16px.png:/del_16px.png + + + Del + + + + + + + ... + + + + :/up_16px.png:/up_16px.png + + + + + + QFrame::NoFrame - + QFrame::Raised - - - 0 + + + + + + + true + + + + Double-click to edit text + + + + + + + + 0 + 1 + + + + + Name - - 6 + + + + Description - - - - ... - - - :/down_16px.png - - - - - - - ... - - - :/del_16px.png - - - Del - - - - - - - QAbstractItemView::AllEditTriggers - - - true - - - QAbstractItemView::SelectRows - - - 3 - - - - - - - - - - ... - - - :/add_16px.png - - - Return - - - - - - - Qt::Vertical - - - - 20 - 131 - - - - - - - - ... - - - :/up_16px.png - - - - + + + + + + + Qt::Horizontal + + + + 157 + 20 + + + + + + + + ... + + + + :/add_16px.png:/add_16px.png + + + Return + + + + + + + Technology Stacks + - - + + + + 1 + 0 + + + QFrame::NoFrame - + QFrame::Raised - - + + 0 - - 6 + + 0 + + + 0 + + + 0 - - - QFrame::NoFrame + + + Connectivity - - QFrame::Raised - - - - 0 - - - 6 - - - - - <html>Computed and symbolic layers (<a href="int:/about/symbolic_layers.xml">See here for details</a>)</html> - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - QFrame::NoFrame + + + + 0 + 1 + - - QFrame::Raised - - - - 0 - - - 6 - - - - - ... - - - :/down_16px.png - - - - - - - ... - - - :/del_16px.png - - - Del - - - - - - - QAbstractItemView::AllEditTriggers - - - true - - - QAbstractItemView::SelectRows - - - 2 - - - - - - - - - ... - - - :/add_16px.png - - - Return - - - - - - - Qt::Vertical - - - - 20 - 131 - - - - - - - - ... - - - :/up_16px.png - - - - + + + + Qt::Vertical + + + + 20 + 0 + + + + - + + + lay::NetTracerConnectivityEditor + QWidget +
layNetTracerConnectivityEditor.h
+ 1 +
+
+ + +
diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerIO.cc b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerConnectivityEditor.cc similarity index 92% rename from src/plugins/tools/net_tracer/lay_plugin/layNetTracerIO.cc rename to src/plugins/tools/net_tracer/lay_plugin/layNetTracerConnectivityEditor.cc index 752e9dabb..83e935546 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerIO.cc +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerConnectivityEditor.cc @@ -21,7 +21,7 @@ */ -#include "layNetTracerIO.h" +#include "layNetTracerConnectivityEditor.h" #include "layNetTracerConfig.h" #include "layConfigurationDialog.h" @@ -56,7 +56,7 @@ class NetTracerConnectivityColumnDelegate : public QItemDelegate { public: - NetTracerConnectivityColumnDelegate (QWidget *parent, db::NetTracerTechnologyComponent *data) + NetTracerConnectivityColumnDelegate (QWidget *parent, db::NetTracerConnectivity *data) : QItemDelegate (parent), mp_data (data) { // .. nothing yet .. @@ -148,7 +148,7 @@ public: } private: - db::NetTracerTechnologyComponent *mp_data; + db::NetTracerConnectivity *mp_data; }; // ----------------------------------------------------------------------------------------- @@ -158,7 +158,7 @@ class NetTracerConnectivitySymbolColumnDelegate : public QItemDelegate { public: - NetTracerConnectivitySymbolColumnDelegate (QWidget *parent, db::NetTracerTechnologyComponent *data) + NetTracerConnectivitySymbolColumnDelegate (QWidget *parent, db::NetTracerConnectivity *data) : QItemDelegate (parent), mp_data (data) { // .. nothing yet .. @@ -262,16 +262,16 @@ public: } private: - db::NetTracerTechnologyComponent *mp_data; + db::NetTracerConnectivity *mp_data; }; // ----------------------------------------------------------------------------------- // NetTracerTechComponentEditor implementation -NetTracerTechComponentEditor::NetTracerTechComponentEditor (QWidget *parent) - : TechnologyComponentEditor (parent) +NetTracerConnectivityEditor::NetTracerConnectivityEditor (QWidget *parent) + : QWidget (parent) { - Ui::NetTracerTechComponentEditor::setupUi (this); + Ui::NetTracerConnectivityEditor::setupUi (this); connect (add_conductor_pb, SIGNAL (clicked ()), this, SLOT (add_clicked ())); connect (del_conductor_pb, SIGNAL (clicked ()), this, SLOT (del_clicked ())); @@ -293,26 +293,16 @@ NetTracerTechComponentEditor::NetTracerTechComponentEditor (QWidget *parent) symbol_table->verticalHeader ()->hide (); } -void -NetTracerTechComponentEditor::commit () +const db::NetTracerConnectivity & +NetTracerConnectivityEditor::get_connectiviy () { - db::NetTracerTechnologyComponent *data = dynamic_cast (tech_component ()); - if (! data) { - return; - } - - *data = m_data; + return m_data; } void -NetTracerTechComponentEditor::setup () +NetTracerConnectivityEditor::set_connectivity (const db::NetTracerConnectivity &data) { - db::NetTracerTechnologyComponent *data = dynamic_cast (tech_component ()); - if (! data) { - return; - } - - m_data = *data; + m_data = data; for (int c = 0; c < 3; ++c) { if (connectivity_table->itemDelegateForColumn (c) != 0) { @@ -332,7 +322,7 @@ NetTracerTechComponentEditor::setup () } void -NetTracerTechComponentEditor::add_clicked () +NetTracerConnectivityEditor::add_clicked () { // removes focus from the tree view - commits the data add_conductor_pb->setFocus (); @@ -351,7 +341,7 @@ NetTracerTechComponentEditor::add_clicked () } void -NetTracerTechComponentEditor::del_clicked () +NetTracerConnectivityEditor::del_clicked () { // removes focus from the tree view - commits the data del_conductor_pb->setFocus (); @@ -374,7 +364,7 @@ NetTracerTechComponentEditor::del_clicked () } void -NetTracerTechComponentEditor::move_up_clicked () +NetTracerConnectivityEditor::move_up_clicked () { // removes focus from the tree view - commits the data move_conductor_up_pb->setFocus (); @@ -391,7 +381,7 @@ NetTracerTechComponentEditor::move_up_clicked () connectivity_table->setCurrentIndex (QModelIndex ()); int n = 0; - for (db::NetTracerTechnologyComponent::iterator l = m_data.begin (); l != m_data.end (); ++l, ++n) { + for (db::NetTracerConnectivity::iterator l = m_data.begin (); l != m_data.end (); ++l, ++n) { if (selected_rows.find (n + 1) != selected_rows.end () && selected_rows.find (n) == selected_rows.end ()) { std::swap (m_data.begin () [n + 1], m_data.begin () [n]); selected_rows.erase (n + 1); @@ -415,7 +405,7 @@ NetTracerTechComponentEditor::move_up_clicked () } void -NetTracerTechComponentEditor::move_down_clicked () +NetTracerConnectivityEditor::move_down_clicked () { // removes focus from the tree view - commits the data move_conductor_down_pb->setFocus (); @@ -432,7 +422,7 @@ NetTracerTechComponentEditor::move_down_clicked () connectivity_table->setCurrentIndex (QModelIndex ()); int n = int (m_data.size ()); - for (db::NetTracerTechnologyComponent::iterator l = m_data.end (); l != m_data.begin (); ) { + for (db::NetTracerConnectivity::iterator l = m_data.end (); l != m_data.begin (); ) { --l; --n; if (selected_rows.find (n - 1) != selected_rows.end () && selected_rows.find (n) == selected_rows.end ()) { @@ -458,7 +448,7 @@ NetTracerTechComponentEditor::move_down_clicked () } void -NetTracerTechComponentEditor::symbol_add_clicked () +NetTracerConnectivityEditor::symbol_add_clicked () { // removes focus from the tree view - commits the data add_symbol_pb->setFocus (); @@ -477,7 +467,7 @@ NetTracerTechComponentEditor::symbol_add_clicked () } void -NetTracerTechComponentEditor::symbol_del_clicked () +NetTracerConnectivityEditor::symbol_del_clicked () { // removes focus from the tree view - commits the data del_symbol_pb->setFocus (); @@ -500,7 +490,7 @@ NetTracerTechComponentEditor::symbol_del_clicked () } void -NetTracerTechComponentEditor::symbol_move_up_clicked () +NetTracerConnectivityEditor::symbol_move_up_clicked () { // removes focus from the tree view - commits the data move_symbol_up_pb->setFocus (); @@ -517,7 +507,7 @@ NetTracerTechComponentEditor::symbol_move_up_clicked () symbol_table->setCurrentIndex (QModelIndex ()); int n = 0; - for (db::NetTracerTechnologyComponent::symbol_iterator l = m_data.begin_symbols (); l != m_data.end_symbols (); ++l, ++n) { + for (db::NetTracerConnectivity::symbol_iterator l = m_data.begin_symbols (); l != m_data.end_symbols (); ++l, ++n) { if (selected_rows.find (n + 1) != selected_rows.end () && selected_rows.find (n) == selected_rows.end ()) { std::swap (m_data.begin_symbols () [n + 1], m_data.begin_symbols () [n]); selected_rows.erase (n + 1); @@ -541,7 +531,7 @@ NetTracerTechComponentEditor::symbol_move_up_clicked () } void -NetTracerTechComponentEditor::symbol_move_down_clicked () +NetTracerConnectivityEditor::symbol_move_down_clicked () { // removes focus from the tree view - commits the data move_symbol_down_pb->setFocus (); @@ -558,7 +548,7 @@ NetTracerTechComponentEditor::symbol_move_down_clicked () symbol_table->setCurrentIndex (QModelIndex ()); int n = int (m_data.symbols ()); - for (db::NetTracerTechnologyComponent::symbol_iterator l = m_data.end_symbols (); l != m_data.begin_symbols (); ) { + for (db::NetTracerConnectivity::symbol_iterator l = m_data.end_symbols (); l != m_data.begin_symbols (); ) { --l; --n; if (selected_rows.find (n - 1) != selected_rows.end () && selected_rows.find (n) == selected_rows.end ()) { @@ -584,7 +574,7 @@ NetTracerTechComponentEditor::symbol_move_down_clicked () } void -NetTracerTechComponentEditor::update () +NetTracerConnectivityEditor::update () { QStringList labels; int n; @@ -600,7 +590,7 @@ NetTracerTechComponentEditor::update () connectivity_table->setHorizontalHeaderLabels (labels); n = 0; - for (db::NetTracerTechnologyComponent::iterator l = m_data.begin (); l != m_data.end (); ++l, ++n) { + for (db::NetTracerConnectivity::iterator l = m_data.begin (); l != m_data.end (); ++l, ++n) { for (int c = 0; c < 3; ++c) { @@ -652,7 +642,7 @@ NetTracerTechComponentEditor::update () symbol_table->setHorizontalHeaderLabels (labels); n = 0; - for (db::NetTracerTechnologyComponent::symbol_iterator l = m_data.begin_symbols (); l != m_data.end_symbols (); ++l, ++n) { + for (db::NetTracerConnectivity::symbol_iterator l = m_data.begin_symbols (); l != m_data.end_symbols (); ++l, ++n) { for (int c = 0; c < 2; ++c) { diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerIO.h b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerConnectivityEditor.h similarity index 75% rename from src/plugins/tools/net_tracer/lay_plugin/layNetTracerIO.h rename to src/plugins/tools/net_tracer/lay_plugin/layNetTracerConnectivityEditor.h index fcb48e989..64377b4a1 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerIO.h +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerConnectivityEditor.h @@ -22,10 +22,10 @@ -#ifndef HDR_layNetTracerIO -#define HDR_layNetTracerIO +#ifndef HDR_layNetTracerConnectivityEditor +#define HDR_layNetTracerConnectivityEditor -#include "ui_NetTracerTechComponentEditor.h" +#include "ui_NetTracerConnectivityEditor.h" #include "dbNetTracer.h" #include "dbNetTracerIO.h" @@ -42,25 +42,23 @@ namespace db { - class NetTracerTechnologyComponent; + class NetTracerConnectivity; } namespace lay { -class FileDialog; - -class NetTracerTechComponentEditor - : public lay::TechnologyComponentEditor, - public Ui::NetTracerTechComponentEditor +class NetTracerConnectivityEditor + : public QWidget, + public Ui::NetTracerConnectivityEditor { Q_OBJECT public: - NetTracerTechComponentEditor (QWidget *parent); + NetTracerConnectivityEditor (QWidget *parent); - void commit (); - void setup (); + void set_connectivity (const db::NetTracerConnectivity &data); + const db::NetTracerConnectivity &get_connectiviy(); public slots: void add_clicked (); @@ -73,7 +71,7 @@ public slots: void symbol_move_down_clicked (); private: - db::NetTracerTechnologyComponent m_data; + db::NetTracerConnectivity m_data; void update (); }; diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.cc b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.cc index d33a14748..454f58330 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.cc +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.cc @@ -290,7 +290,7 @@ NetTracerDialog::get_net_tracer_setup (const lay::CellView &cv, db::NetTracerDat if (! tech) { return false; } - const db::NetTracerTechnologyComponent *tech_component = dynamic_cast (tech->component_by_name (db::net_tracer_component_name ())); + const db::NetTracerConnectivity *tech_component = dynamic_cast (tech->component_by_name (db::net_tracer_component_name ())); if (! tech_component) { return false; } diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerPlugin.cc b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerPlugin.cc index 7cc918cd9..d4aa6d75a 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerPlugin.cc +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerPlugin.cc @@ -22,7 +22,7 @@ #include "dbNetTracerIO.h" -#include "layNetTracerIO.h" +#include "layNetTracerTechComponentEditor.h" #include "layNetTracerDialog.h" #include "layNetTracerConfig.h" diff --git a/src/plugins/tools/net_tracer/lay_plugin/lay_plugin.pro b/src/plugins/tools/net_tracer/lay_plugin/lay_plugin.pro index 7f05e1cc4..1216f559a 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/lay_plugin.pro +++ b/src/plugins/tools/net_tracer/lay_plugin/lay_plugin.pro @@ -14,17 +14,20 @@ LIBS += -L$$DESTDIR/../db_plugins -lnet_tracer HEADERS = \ layNetTracerConfig.h \ + layNetTracerConnectivityEditor.h \ layNetTracerDialog.h \ - layNetTracerIO.h \ + layNetTracerTechComponentEditor.h SOURCES = \ layNetTracerConfig.cc \ + layNetTracerConnectivityEditor.cc \ layNetTracerDialog.cc \ layNetTracerPlugin.cc \ - layNetTracerIO.cc \ + layNetTracerTechComponentEditor.cc FORMS = \ NetTracerConfigPage.ui \ + NetTracerConnectivityEditor.ui \ NetTracerDialog.ui \ NetTracerTechComponentEditor.ui \ diff --git a/src/plugins/tools/net_tracer/unit_tests/dbNetTracerTests.cc b/src/plugins/tools/net_tracer/unit_tests/dbNetTracerTests.cc index 52375e61f..e0fa275a5 100644 --- a/src/plugins/tools/net_tracer/unit_tests/dbNetTracerTests.cc +++ b/src/plugins/tools/net_tracer/unit_tests/dbNetTracerTests.cc @@ -76,21 +76,21 @@ static db::NetTracerShape find_shape (const db::Layout &layout, const db::Cell & } #endif -static db::NetTracerNet trace (db::NetTracer &tracer, const db::Layout &layout, const db::Cell &cell, const db::NetTracerTechnologyComponent &tc, unsigned int l_start, const db::Point &p_start) +static db::NetTracerNet trace (db::NetTracer &tracer, const db::Layout &layout, const db::Cell &cell, const db::NetTracerConnectivity &tc, unsigned int l_start, const db::Point &p_start) { db::NetTracerData tracer_data = tc.get_tracer_data (layout); tracer.trace (layout, cell, p_start, l_start, tracer_data); return db::NetTracerNet (tracer, db::ICplxTrans (), layout, cell.cell_index (), std::string (), std::string (), tracer_data); } -static db::NetTracerNet trace (db::NetTracer &tracer, const db::Layout &layout, const db::Cell &cell, const db::NetTracerTechnologyComponent &tc, unsigned int l_start, const db::Point &p_start, unsigned int l_stop, const db::Point &p_stop) +static db::NetTracerNet trace (db::NetTracer &tracer, const db::Layout &layout, const db::Cell &cell, const db::NetTracerConnectivity &tc, unsigned int l_start, const db::Point &p_start, unsigned int l_stop, const db::Point &p_stop) { db::NetTracerData tracer_data = tc.get_tracer_data (layout); tracer.trace (layout, cell, p_start, l_start, p_stop, l_stop, tracer_data); return db::NetTracerNet (tracer, db::ICplxTrans (), layout, cell.cell_index (), std::string (), std::string (), tracer_data); } -void run_test (tl::TestBase *_this, const std::string &file, const db::NetTracerTechnologyComponent &tc, const db::LayerProperties &lp_start, const db::Point &p_start, const std::string &file_au, const char *net_name = 0, size_t depth = 0) +void run_test (tl::TestBase *_this, const std::string &file, const db::NetTracerConnectivity &tc, const db::LayerProperties &lp_start, const db::Point &p_start, const std::string &file_au, const char *net_name = 0, size_t depth = 0) { db::Manager m (false); @@ -130,7 +130,7 @@ void run_test (tl::TestBase *_this, const std::string &file, const db::NetTracer db::compare_layouts (_this, layout_net, fn, db::WriteOAS); } -void run_test2 (tl::TestBase *_this, const std::string &file, const db::NetTracerTechnologyComponent &tc, const db::LayerProperties &lp_start, const db::Point &p_start, const db::LayerProperties &lp_stop, const db::Point &p_stop, const std::string &file_au, const char *net_name = 0) +void run_test2 (tl::TestBase *_this, const std::string &file, const db::NetTracerConnectivity &tc, const db::LayerProperties &lp_start, const db::Point &p_start, const db::LayerProperties &lp_stop, const db::Point &p_stop, const std::string &file_au, const char *net_name = 0) { db::Manager m (false); @@ -169,7 +169,7 @@ TEST(1) std::string file = "t1.oas.gz"; std::string file_au = "t1_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0", "2/0", "3/0")); run_test (_this, file, tc, db::LayerProperties (1, 0), db::Point (7000, 1500), file_au, "THE_NAME"); @@ -180,7 +180,7 @@ TEST(1b) std::string file = "t1.oas.gz"; std::string file_au = "t1b_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0", "2/0", "3/0")); // point is off net ... @@ -192,7 +192,7 @@ TEST(1c) std::string file = "t1.oas.gz"; std::string file_au = "t1_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add_symbol (symbol ("a", "1/0")); tc.add_symbol (symbol ("c", "cc")); tc.add_symbol (symbol ("cc", "3/0")); @@ -206,7 +206,7 @@ TEST(1d) std::string file = "t1.oas.gz"; std::string file_au = "t1d_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0", "10/0", "11/0")); // some layers are non-existing @@ -218,7 +218,7 @@ TEST(2) std::string file = "t2.oas.gz"; std::string file_au = "t2_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0", "2/0", "3/0")); run_test2 (_this, file, tc, db::LayerProperties (1, 0), db::Point (7000, 1500), db::LayerProperties (3, 0), db::Point (4000, -20000), file_au, "THE_NAME"); @@ -229,7 +229,7 @@ TEST(3) std::string file = "t3.oas.gz"; std::string file_au = "t3_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0", "2/0", "3/0")); std::string msg; @@ -246,7 +246,7 @@ TEST(4) std::string file = "t4.oas.gz"; std::string file_au = "t4_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0", "2/0", "3/0")); run_test (_this, file, tc, db::LayerProperties (1, 0), db::Point (7000, 1500), file_au, ""); @@ -257,7 +257,7 @@ TEST(4b) std::string file = "t4.oas.gz"; std::string file_au = "t4b_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0", "3/0")); run_test (_this, file, tc, db::LayerProperties (1, 0), db::Point (7000, 1500), file_au, "THE_NAME"); @@ -268,7 +268,7 @@ TEST(5) std::string file = "t5.oas.gz"; std::string file_au = "t5_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0*10/0", "2/0", "3/0")); run_test (_this, file, tc, db::LayerProperties (1, 0), db::Point (7000, 1500), file_au, "THE_NAME"); @@ -279,7 +279,7 @@ TEST(5b) std::string file = "t5.oas.gz"; std::string file_au = "t5b_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0", "2/0*10/0", "3/0")); run_test (_this, file, tc, db::LayerProperties (1, 0), db::Point (7000, 1500), file_au, "THE_NAME"); @@ -290,7 +290,7 @@ TEST(5c) std::string file = "t5.oas.gz"; std::string file_au = "t5c_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0", "2/0-11/0", "3/0")); run_test (_this, file, tc, db::LayerProperties (1, 0), db::Point (7000, 1500), file_au, ""); @@ -301,7 +301,7 @@ TEST(5d) std::string file = "t5.oas.gz"; std::string file_au = "t5d_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0-12/0", "2/0", "3/0-12/0")); run_test (_this, file, tc, db::LayerProperties (1, 0), db::Point (7000, 1500), file_au, "THE_NAME"); @@ -312,7 +312,7 @@ TEST(5e) std::string file = "t5.oas.gz"; std::string file_au = "t5e_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0-12/0", "2/0", "3/0-12/0")); run_test (_this, file, tc, db::LayerProperties (1, 0), db::Point (7000, 1500), file_au, "THE_NAME"); @@ -323,7 +323,7 @@ TEST(5f) std::string file = "t5.oas.gz"; std::string file_au = "t5f_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add_symbol (symbol ("x", "3-14")); tc.add (connection ("10-13", "x")); tc.add (connection ("x", "2", "1+13")); @@ -336,7 +336,7 @@ TEST(6) std::string file = "t6.oas.gz"; std::string file_au = "t6_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1-10", "2", "3")); tc.add (connection ("3", "4", "5")); @@ -348,7 +348,7 @@ TEST(6b) std::string file = "t6.oas.gz"; std::string file_au = "t6b_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1-10", "2", "3")); tc.add (connection ("3", "4", "5")); @@ -360,7 +360,7 @@ TEST(7) std::string file = "t7.oas.gz"; std::string file_au = "t7_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("15", "14", "2-7")); tc.add (connection ("15", "14", "7")); @@ -373,7 +373,7 @@ TEST(8) std::string file = "t8.oas.gz"; std::string file_au = "t8_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("15", "14", "7")); run_test (_this, file, tc, db::LayerProperties (15, 0), db::Point (4000, 10000), file_au, ""); @@ -384,7 +384,7 @@ TEST(9) std::string file = "t9.oas.gz"; std::string file_au = "t9_net.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add_symbol (symbol ("a", "8-12")); tc.add_symbol (symbol ("b", "a+7")); tc.add_symbol (symbol ("c", "15*26")); diff --git a/src/plugins/tools/net_tracer/unit_tests/dbTraceAllNets.cc b/src/plugins/tools/net_tracer/unit_tests/dbTraceAllNets.cc index 53b674871..2ed796f0c 100644 --- a/src/plugins/tools/net_tracer/unit_tests/dbTraceAllNets.cc +++ b/src/plugins/tools/net_tracer/unit_tests/dbTraceAllNets.cc @@ -50,7 +50,7 @@ static db::NetTracerSymbolInfo symbol (const std::string &s, const std::string & return db::NetTracerSymbolInfo (s, e); } -void run_test (tl::TestBase *_this, const std::string &file, const db::NetTracerTechnologyComponent &tc, const std::string &file_au) +void run_test (tl::TestBase *_this, const std::string &file, const db::NetTracerConnectivity &tc, const std::string &file_au) { db::Manager m (false); @@ -93,7 +93,7 @@ TEST(1) std::string file = "t1.oas.gz"; std::string file_au = "t1_all_nets.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0", "2/0", "3/0")); run_test (_this, file, tc, file_au); @@ -104,7 +104,7 @@ TEST(1c) std::string file = "t1.oas.gz"; std::string file_au = "t1_all_nets.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add_symbol (symbol ("a", "1/0")); tc.add_symbol (symbol ("c", "cc")); tc.add_symbol (symbol ("cc", "3/0")); @@ -118,7 +118,7 @@ TEST(1d) std::string file = "t1.oas.gz"; std::string file_au = "t1d_all_nets.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0", "10/0", "11/0")); // some layers are non-existing @@ -130,7 +130,7 @@ TEST(4) std::string file = "t4.oas.gz"; std::string file_au = "t4_all_nets.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0", "2/0", "3/0")); run_test (_this, file, tc, file_au); @@ -141,7 +141,7 @@ TEST(4b) std::string file = "t4.oas.gz"; std::string file_au = "t4b_all_nets.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0", "3/0")); run_test (_this, file, tc, file_au); @@ -152,7 +152,7 @@ TEST(5) std::string file = "t5.oas.gz"; std::string file_au = "t5_all_nets.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0*10/0", "2/0", "3/0")); run_test (_this, file, tc, file_au); @@ -163,7 +163,7 @@ TEST(5b) std::string file = "t5.oas.gz"; std::string file_au = "t5b_all_nets.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0", "2/0*10/0", "3/0")); run_test (_this, file, tc, file_au); @@ -174,7 +174,7 @@ TEST(5c) std::string file = "t5.oas.gz"; std::string file_au = "t5c_all_nets.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0", "2/0-11/0", "3/0")); run_test (_this, file, tc, file_au); @@ -185,7 +185,7 @@ TEST(5d) std::string file = "t5.oas.gz"; std::string file_au = "t5d_all_nets.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1/0-12/0", "2/0", "3/0-12/0")); run_test (_this, file, tc, file_au); @@ -196,7 +196,7 @@ TEST(5f) std::string file = "t5.oas.gz"; std::string file_au = "t5f_all_nets.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add_symbol (symbol ("x", "3-14")); tc.add (connection ("10-13", "x")); tc.add (connection ("x", "2", "1+13")); @@ -209,7 +209,7 @@ TEST(6) std::string file = "t6.oas.gz"; std::string file_au = "t6_all_nets.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("1-10", "2", "3")); tc.add (connection ("3", "4", "5")); @@ -221,7 +221,7 @@ TEST(7) std::string file = "t7.oas.gz"; std::string file_au = "t7_all_nets.oas.gz"; - db::NetTracerTechnologyComponent tc; + db::NetTracerConnectivity tc; tc.add (connection ("15", "14", "2-7")); tc.add (connection ("15", "14", "7")); From 40786d13e0818581a5622c2f94860f45e8e95e0b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 Sep 2022 00:36:29 +0200 Subject: [PATCH 04/54] WIP: added missing files --- .../lay_plugin/NetTracerConnectivityEditor.ui | 343 ++++++++++++++++++ .../layNetTracerTechComponentEditor.cc | 310 ++++++++++++++++ .../layNetTracerTechComponentEditor.h | 81 +++++ 3 files changed, 734 insertions(+) create mode 100644 src/plugins/tools/net_tracer/lay_plugin/NetTracerConnectivityEditor.ui create mode 100644 src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc create mode 100644 src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.h diff --git a/src/plugins/tools/net_tracer/lay_plugin/NetTracerConnectivityEditor.ui b/src/plugins/tools/net_tracer/lay_plugin/NetTracerConnectivityEditor.ui new file mode 100644 index 000000000..62af9ff85 --- /dev/null +++ b/src/plugins/tools/net_tracer/lay_plugin/NetTracerConnectivityEditor.ui @@ -0,0 +1,343 @@ + + + NetTracerConnectivityEditor + + + + 0 + 0 + 572 + 449 + + + + Form + + + + 6 + + + 9 + + + + + Qt::Vertical + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + + + <html>Connectivity (<a href="int:/about/connectivity.xml">See here for details</a>)</html> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 6 + + + + + ... + + + + :/down_16px.png:/down_16px.png + + + + + + + ... + + + + :/del_16px.png:/del_16px.png + + + Del + + + + + + + QAbstractItemView::AllEditTriggers + + + true + + + QAbstractItemView::SelectRows + + + 3 + + + + + + + + + + ... + + + + :/add_16px.png:/add_16px.png + + + Return + + + + + + + Qt::Vertical + + + + 20 + 131 + + + + + + + + ... + + + + :/up_16px.png:/up_16px.png + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + + + <html>Computed and symbolic layers (<a href="int:/about/symbolic_layers.xml">See here for details</a>)</html> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 6 + + + + + ... + + + + :/down_16px.png:/down_16px.png + + + + + + + ... + + + + :/del_16px.png:/del_16px.png + + + Del + + + + + + + QAbstractItemView::AllEditTriggers + + + true + + + QAbstractItemView::SelectRows + + + 2 + + + + + + + + + ... + + + + :/add_16px.png:/add_16px.png + + + Return + + + + + + + Qt::Vertical + + + + 20 + 131 + + + + + + + + ... + + + + :/up_16px.png:/up_16px.png + + + + + + + + + + + + + + + + + diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc new file mode 100644 index 000000000..3d8293d78 --- /dev/null +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc @@ -0,0 +1,310 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2022 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 "layNetTracerTechComponentEditor.h" +#include "layNetTracerConfig.h" + +#include "layConfigurationDialog.h" +#include "laybasicConfig.h" +#include "layConverters.h" +#include "layFinder.h" +#include "layLayoutView.h" +#include "layTechSetupDialog.h" +#include "layFileDialog.h" +#include "layQtTools.h" +#include "tlExceptions.h" +#include "tlXMLWriter.h" +#include "tlUtils.h" +#include "gsiDecl.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace lay +{ + +// @@@ TODO: edit on double-click + +// ----------------------------------------------------------------------------------- +// NetTracerTechComponentEditor implementation + +NetTracerTechComponentEditor::NetTracerTechComponentEditor (QWidget *parent) + : TechnologyComponentEditor (parent) +{ + Ui::NetTracerTechComponentEditor::setupUi (this); + + connect (add_pb, SIGNAL (clicked ()), this, SLOT (add_clicked ())); + connect (del_pb, SIGNAL (clicked ()), this, SLOT (del_clicked ())); + connect (move_up_pb, SIGNAL (clicked ()), this, SLOT (move_up_clicked ())); + connect (move_down_pb, SIGNAL (clicked ()), this, SLOT (move_down_clicked ())); + + stack_tree->header ()->setHighlightSections (false); + stack_tree->header ()->setStretchLastSection (true); + + connect (stack_tree, SIGNAL (currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT (current_item_changed(QTreeWidgetItem *, QTreeWidgetItem *))); + connect (stack_tree, SIGNAL (itemChanged(QTreeWidgetItem *, int)), this, SLOT (item_changed(QTreeWidgetItem *, int))); +} + +void +NetTracerTechComponentEditor::commit () +{ + db::NetTracerTechnologyComponent *data = dynamic_cast (tech_component ()); + if (! data) { + return; + } + + *data = m_data; +} + +void +NetTracerTechComponentEditor::setup () +{ + db::NetTracerTechnologyComponent *data = dynamic_cast (tech_component ()); + if (! data) { + return; + } + + m_data = *data; + + if (m_data.size () == 0) { + m_data.push_back (db::NetTracerConnectivity ()); + } + + update (); +} + +void +NetTracerTechComponentEditor::item_changed (QTreeWidgetItem *item, int column) +{ + int row = stack_tree->indexOfTopLevelItem (item); + if (row >= 0 && row < int (m_data.size ())) { + if (column == 0) { + std::string n = tl::to_string (item->data (column, Qt::EditRole)); + m_data.begin ()[row].set_name (n); + if (n.empty ()) { + item->setData (column, Qt::DisplayRole, tr ("(default)")); + } else { + item->setData (column, Qt::DisplayRole, tl::to_qstring (n)); + } + } + if (column == 1) { + m_data.begin ()[row].set_description (tl::to_string (item->data (column, Qt::EditRole))); + } + } +} + +void +NetTracerTechComponentEditor::current_item_changed (QTreeWidgetItem *current, QTreeWidgetItem *previous) +{ + commit_current (previous); + + int row = current ? stack_tree->indexOfTopLevelItem (current) : -1; + if (row < 0 || row >= int (m_data.size ())) { + connectivity_editor_widget->set_connectivity (db::NetTracerConnectivity ()); + connectivity_editor_widget->hide (); + } else { + connectivity_editor_widget->set_connectivity (m_data.begin ()[row]); + connectivity_editor_widget->show (); + } +} + +void +NetTracerTechComponentEditor::commit_current () +{ + commit_current (stack_tree->currentItem ()); +} + +void +NetTracerTechComponentEditor::commit_current (QTreeWidgetItem *current) +{ + int row = current ? stack_tree->indexOfTopLevelItem (current) : -1; + if (row >= 0 && row < int (m_data.size ())) { + m_data.begin () [row] = connectivity_editor_widget->get_connectiviy (); + } +} + +void +NetTracerTechComponentEditor::add_clicked () +{ + // removes focus from the tree view - commits the data + add_pb->setFocus (); + commit_current (); + + int row = stack_tree->currentItem () ? stack_tree->indexOfTopLevelItem (stack_tree->currentItem ()) : -1; + if (row < 0) { + m_data.push_back (db::NetTracerConnectivity ()); + row = int (m_data.size () - 1); + } else { + row += 1; + m_data.insert (m_data.begin () + row, db::NetTracerConnectivity ()); + } + + update (); + stack_tree->setCurrentItem (stack_tree->topLevelItem (row)); +} + +void +NetTracerTechComponentEditor::del_clicked () +{ + // removes focus from the tree view - commits the data + del_pb->setFocus (); + commit_current (); + + std::set selected_rows; + QModelIndexList selected_indices = stack_tree->selectionModel ()->selectedIndexes (); + for (auto i = selected_indices.begin (); i != selected_indices.end (); ++i) { + selected_rows.insert (i->row ()); + } + + stack_tree->setCurrentIndex (QModelIndex ()); + + int offset = 0; + for (std::set::const_iterator r = selected_rows.begin (); r != selected_rows.end (); ++r) { + m_data.erase (m_data.begin () + (*r - offset)); + ++offset; + } + + update (); +} + +void +NetTracerTechComponentEditor::move_up_clicked () +{ + // removes focus from the tree view - commits the data + move_up_pb->setFocus (); + commit_current (); + + std::set selected_rows; + QModelIndexList selected_indices = stack_tree->selectionModel ()->selectedIndexes (); + for (auto i = selected_indices.begin (); i != selected_indices.end (); ++i) { + selected_rows.insert (i->row ()); + } + + QTreeWidgetItem *current = stack_tree->currentItem (); + int n_current = current ? current->data (0, Qt::UserRole).toInt () : -1; + + stack_tree->setCurrentIndex (QModelIndex ()); + + int n = 0; + for (db::NetTracerTechnologyComponent::iterator l = m_data.begin (); l != m_data.end (); ++l, ++n) { + if (selected_rows.find (n + 1) != selected_rows.end () && selected_rows.find (n) == selected_rows.end ()) { + std::swap (m_data.begin () [n + 1], m_data.begin () [n]); + selected_rows.erase (n + 1); + selected_rows.insert (n); + if (n_current == n + 1) { + n_current = n; + } + } + } + + update (); + + // select the new items + for (std::set ::const_iterator s = selected_rows.begin (); s != selected_rows.end (); ++s) { + stack_tree->selectionModel ()->select (stack_tree->model ()->index (*s, 0), QItemSelectionModel::Select | QItemSelectionModel::Rows); + } + + if (n_current >= 0) { + stack_tree->selectionModel ()->select (stack_tree->model ()->index (n_current, 0), QItemSelectionModel::Current | QItemSelectionModel::Rows); + } +} + +void +NetTracerTechComponentEditor::move_down_clicked () +{ + // removes focus from the tree view - commits the data + move_down_pb->setFocus (); + commit_current (); + + std::set selected_rows; + QModelIndexList selected_indices = stack_tree->selectionModel ()->selectedIndexes (); + for (auto i = selected_indices.begin (); i != selected_indices.end (); ++i) { + selected_rows.insert (i->row ()); + } + + QTreeWidgetItem *current = stack_tree->currentItem (); + int n_current = current ? current->data (0, Qt::UserRole).toInt () : -1; + + stack_tree->setCurrentIndex (QModelIndex ()); + + int n = int (m_data.size ()); + for (db::NetTracerTechnologyComponent::iterator l = m_data.end (); l != m_data.begin (); ) { + --l; + --n; + if (selected_rows.find (n - 1) != selected_rows.end () && selected_rows.find (n) == selected_rows.end ()) { + std::swap (m_data.begin () [n - 1], m_data.begin () [n]); + selected_rows.erase (n - 1); + selected_rows.insert (n); + if (n_current == n - 1) { + n_current = n; + } + } + } + + update (); + + // select the new items + for (std::set ::const_iterator s = selected_rows.begin (); s != selected_rows.end (); ++s) { + stack_tree->selectionModel ()->select (stack_tree->model ()->index (*s, 0), QItemSelectionModel::Select | QItemSelectionModel::Rows); + } + + if (n_current >= 0) { + stack_tree->selectionModel ()->select (stack_tree->model ()->index (n_current, 0), QItemSelectionModel::Current | QItemSelectionModel::Rows); + } +} + +void +NetTracerTechComponentEditor::update () +{ + stack_tree->clear (); + stack_tree->clearSelection (); + + int n = 0; + for (db::NetTracerTechnologyComponent::iterator l = m_data.begin (); l != m_data.end (); ++l, ++n) { + + QTreeWidgetItem *item = new QTreeWidgetItem (stack_tree); + item->setFlags (item->flags () | Qt::ItemIsEditable); + + if (l->name ().empty ()) { + item->setData (0, Qt::DisplayRole, QVariant (tr ("(default)"))); + } else { + item->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (l->name ()))); + } + item->setData (0, Qt::EditRole, QVariant (tl::to_qstring (l->name ()))); + item->setData (1, Qt::DisplayRole, QVariant (tl::to_qstring (l->description ()))); + + item->setData (0, Qt::UserRole, QVariant (n)); + + } + + current_item_changed (0, 0); +} + +} + diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.h b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.h new file mode 100644 index 000000000..db802fe6b --- /dev/null +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.h @@ -0,0 +1,81 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2022 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_layNetTracerTechComponentEditor +#define HDR_layNetTracerTechComponentEditor + +#include "ui_NetTracerTechComponentEditor.h" + +#include "dbNetTracer.h" +#include "dbNetTracerIO.h" +#include "dbTechnology.h" + +#include "layNetTracerConfig.h" +#include "layBrowser.h" +#include "layPlugin.h" +#include "layViewObject.h" +#include "layMarker.h" +#include "layTechnology.h" + +#include "tlObject.h" + +namespace db +{ + class NetTracerTechnologyComponent; +} + +namespace lay +{ + +class NetTracerTechComponentEditor + : public lay::TechnologyComponentEditor, + public Ui::NetTracerTechComponentEditor +{ +Q_OBJECT + +public: + NetTracerTechComponentEditor (QWidget *parent); + + void commit (); + void setup (); + +public slots: + void add_clicked (); + void del_clicked (); + void move_up_clicked (); + void move_down_clicked (); + void current_item_changed (QTreeWidgetItem *current, QTreeWidgetItem *previous); + void item_changed (QTreeWidgetItem *item, int column); +private: + db::NetTracerTechnologyComponent m_data; + + void update (); + void commit_current (QTreeWidgetItem *current); + void commit_current (); +}; + +} + +#endif + From 2505ebb9a3c407b4c54963aa1af241d2674e5eb2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 Sep 2022 10:53:23 +0200 Subject: [PATCH 05/54] WIP: some debugging --- .../net_tracer/db_plugin/dbNetTracerIO.h | 2 +- .../NetTracerTechComponentEditor.ui | 3 + .../layNetTracerTechComponentEditor.cc | 113 ++++++++++++++---- 3 files changed, 91 insertions(+), 27 deletions(-) diff --git a/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.h b/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.h index 82dedb2c1..422584323 100644 --- a/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.h +++ b/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.h @@ -534,7 +534,7 @@ public: iterator end () { - return m_connectivity.begin (); + return m_connectivity.end (); } db::NetTracerTechnologyComponent *clone () const diff --git a/src/plugins/tools/net_tracer/lay_plugin/NetTracerTechComponentEditor.ui b/src/plugins/tools/net_tracer/lay_plugin/NetTracerTechComponentEditor.ui index 8ee37634a..825513c9f 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/NetTracerTechComponentEditor.ui +++ b/src/plugins/tools/net_tracer/lay_plugin/NetTracerTechComponentEditor.ui @@ -105,6 +105,9 @@ 1 + + false + Name diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc index 3d8293d78..cfe49d8d4 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc @@ -49,7 +49,83 @@ namespace lay { -// @@@ TODO: edit on double-click +// ----------------------------------------------------------------------------------------- +// NetTracerTechComponentColumnDelegate definition and implementation + +class NetTracerTechComponentColumnDelegate + : public QItemDelegate +{ +public: + NetTracerTechComponentColumnDelegate (QWidget *parent, db::NetTracerTechnologyComponent *data) + : QItemDelegate (parent), mp_data (data) + { + // .. nothing yet .. + } + + QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem & /*option*/, const QModelIndex & /*index*/) const + { + return new QLineEdit (parent); + } + + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex & /*index*/) const + { + editor->setGeometry(option.rect); + } + + void setEditorData (QWidget *widget, const QModelIndex &index) const + { + QLineEdit *editor = dynamic_cast (widget); + if (editor) { + int n = index.model ()->data (index, Qt::UserRole).toInt (); + if (mp_data->size () > size_t (n)) { + if (index.column () == 0) { + std::string name = mp_data->begin () [n].name (); + editor->setText (tl::to_qstring (name)); + editor->setPlaceholderText (tr ("(default)")); + } else if (index.column () == 1) { + editor->setText (tl::to_qstring (mp_data->begin () [n].description ())); + } + } + } + } + + void setModelData (QWidget *widget, QAbstractItemModel *model, const QModelIndex &index) const + { + QLineEdit *editor = dynamic_cast (widget); + if (editor) { + + int n = model->data (index, Qt::UserRole).toInt (); + if (mp_data->size () > size_t (n)) { + + std::string text = tl::to_string (editor->text ()); + if (index.column () == 0 && text.empty ()) { + model->setData (index, QVariant (tr ("(default)")), Qt::DisplayRole); + } else { + model->setData (index, QVariant (tl::to_qstring (text)), Qt::DisplayRole); + } + + if (index.column () == 0) { + mp_data->begin () [n].set_name (text); + } else if (index.column () == 1) { + mp_data->begin () [n].set_description (text); + } + + } + + } + } + + QSize sizeHint (const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QWidget *editor = createEditor (0, option, index); + QSize size = editor->sizeHint (); + delete editor; + return size - QSize (2, 2); + } + +private: + db::NetTracerTechnologyComponent *mp_data; +}; // ----------------------------------------------------------------------------------- // NetTracerTechComponentEditor implementation @@ -68,7 +144,6 @@ NetTracerTechComponentEditor::NetTracerTechComponentEditor (QWidget *parent) stack_tree->header ()->setStretchLastSection (true); connect (stack_tree, SIGNAL (currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT (current_item_changed(QTreeWidgetItem *, QTreeWidgetItem *))); - connect (stack_tree, SIGNAL (itemChanged(QTreeWidgetItem *, int)), this, SLOT (item_changed(QTreeWidgetItem *, int))); } void @@ -96,27 +171,10 @@ NetTracerTechComponentEditor::setup () m_data.push_back (db::NetTracerConnectivity ()); } - update (); -} + stack_tree->setItemDelegateForColumn (0, new NetTracerTechComponentColumnDelegate (stack_tree, &m_data)); + stack_tree->setItemDelegateForColumn (1, new NetTracerTechComponentColumnDelegate (stack_tree, &m_data)); -void -NetTracerTechComponentEditor::item_changed (QTreeWidgetItem *item, int column) -{ - int row = stack_tree->indexOfTopLevelItem (item); - if (row >= 0 && row < int (m_data.size ())) { - if (column == 0) { - std::string n = tl::to_string (item->data (column, Qt::EditRole)); - m_data.begin ()[row].set_name (n); - if (n.empty ()) { - item->setData (column, Qt::DisplayRole, tr ("(default)")); - } else { - item->setData (column, Qt::DisplayRole, tl::to_qstring (n)); - } - } - if (column == 1) { - m_data.begin ()[row].set_description (tl::to_string (item->data (column, Qt::EditRole))); - } - } + update (); } void @@ -291,19 +349,22 @@ NetTracerTechComponentEditor::update () QTreeWidgetItem *item = new QTreeWidgetItem (stack_tree); item->setFlags (item->flags () | Qt::ItemIsEditable); - if (l->name ().empty ()) { + std::string name = l->name (); + if (name.empty ()) { item->setData (0, Qt::DisplayRole, QVariant (tr ("(default)"))); } else { - item->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (l->name ()))); + item->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (name))); } - item->setData (0, Qt::EditRole, QVariant (tl::to_qstring (l->name ()))); item->setData (1, Qt::DisplayRole, QVariant (tl::to_qstring (l->description ()))); item->setData (0, Qt::UserRole, QVariant (n)); } - current_item_changed (0, 0); + if (! stack_tree->currentItem () && stack_tree->topLevelItemCount () > 0) { + stack_tree->setCurrentItem (stack_tree->topLevelItem (0)); + } + current_item_changed (stack_tree->currentItem (), 0); } } From 8919916da91ba9f92cf594fbaee569e8675c23ab Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 Sep 2022 11:22:45 +0200 Subject: [PATCH 06/54] WIP: some debugging --- .../tools/net_tracer/db_plugin/dbNetTracerPlugin.cc | 2 ++ .../lay_plugin/layNetTracerConnectivityEditor.cc | 10 +++++++--- .../lay_plugin/layNetTracerConnectivityEditor.h | 2 +- .../lay_plugin/layNetTracerTechComponentEditor.cc | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/plugins/tools/net_tracer/db_plugin/dbNetTracerPlugin.cc b/src/plugins/tools/net_tracer/db_plugin/dbNetTracerPlugin.cc index 4c5632c23..ed67acd05 100644 --- a/src/plugins/tools/net_tracer/db_plugin/dbNetTracerPlugin.cc +++ b/src/plugins/tools/net_tracer/db_plugin/dbNetTracerPlugin.cc @@ -122,6 +122,8 @@ public: FallbackXMLWriteAdapator (&NetTracerConnectivity::add_symbol), "symbols") + // 0.28 definitions tl::make_element ((NetTracerTechnologyComponent::const_iterator (NetTracerTechnologyComponent::*) () const) &NetTracerTechnologyComponent::begin, (NetTracerTechnologyComponent::const_iterator (NetTracerTechnologyComponent::*) () const) &NetTracerTechnologyComponent::end, (void (NetTracerTechnologyComponent::*) (const NetTracerConnectivity &)) &NetTracerTechnologyComponent::push_back, "connectivity", + tl::make_member (&NetTracerConnectivity::name, &NetTracerConnectivity::set_name, "name") + + tl::make_member (&NetTracerConnectivity::description, &NetTracerConnectivity::set_description, "description") + tl::make_member ((NetTracerConnectivity::const_iterator (NetTracerConnectivity::*) () const) &NetTracerConnectivity::begin, (NetTracerConnectivity::const_iterator (NetTracerConnectivity::*) () const) &NetTracerConnectivity::end, &NetTracerConnectivity::add, "connection") + tl::make_member ((NetTracerConnectivity::const_symbol_iterator (NetTracerConnectivity::*) () const) &NetTracerConnectivity::begin_symbols, (NetTracerConnectivity::const_symbol_iterator (NetTracerConnectivity::*) () const) &NetTracerConnectivity::end_symbols, &NetTracerConnectivity::add_symbol, "symbols") ) diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerConnectivityEditor.cc b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerConnectivityEditor.cc index 83e935546..5458327ec 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerConnectivityEditor.cc +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerConnectivityEditor.cc @@ -293,10 +293,14 @@ NetTracerConnectivityEditor::NetTracerConnectivityEditor (QWidget *parent) symbol_table->verticalHeader ()->hide (); } -const db::NetTracerConnectivity & -NetTracerConnectivityEditor::get_connectiviy () +void +NetTracerConnectivityEditor::get_connectivity (db::NetTracerConnectivity &data) { - return m_data; + std::string name = data.name (); + std::string description = data.description (); + data = m_data; + data.set_name (name); + data.set_description (description); } void diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerConnectivityEditor.h b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerConnectivityEditor.h index 64377b4a1..beed78303 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerConnectivityEditor.h +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerConnectivityEditor.h @@ -58,7 +58,7 @@ public: NetTracerConnectivityEditor (QWidget *parent); void set_connectivity (const db::NetTracerConnectivity &data); - const db::NetTracerConnectivity &get_connectiviy(); + void get_connectivity (db::NetTracerConnectivity &); public slots: void add_clicked (); diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc index cfe49d8d4..662f5e235 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc @@ -203,7 +203,7 @@ NetTracerTechComponentEditor::commit_current (QTreeWidgetItem *current) { int row = current ? stack_tree->indexOfTopLevelItem (current) : -1; if (row >= 0 && row < int (m_data.size ())) { - m_data.begin () [row] = connectivity_editor_widget->get_connectiviy (); + connectivity_editor_widget->get_connectivity (m_data.begin () [row]); } } From 511f55d4da5dc7a5f273ae289e6afcc9039f0e30 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 Sep 2022 16:33:23 +0200 Subject: [PATCH 07/54] WIP: refined solution - some bug fixes, XML file is backward compatible now --- src/icons/icons.qrc | 2 + src/icons/images/clone_16px.png | Bin 0 -> 411 bytes src/icons/images/clone_16px@2x.png | Bin 0 -> 673 bytes src/icons/svg/clone_16px.svg | 93 +++++++++++++ src/lay/lay/TechSetupDialog.ui | 38 ++++-- .../net_tracer/db_plugin/dbNetTracerIO.h | 12 +- .../net_tracer/db_plugin/dbNetTracerPlugin.cc | 100 +++++++++++--- .../NetTracerTechComponentEditor.ui | 129 ++++++++++++------ .../layNetTracerTechComponentEditor.cc | 70 +++++++++- .../layNetTracerTechComponentEditor.h | 1 + 10 files changed, 363 insertions(+), 82 deletions(-) create mode 100644 src/icons/images/clone_16px.png create mode 100644 src/icons/images/clone_16px@2x.png create mode 100644 src/icons/svg/clone_16px.svg diff --git a/src/icons/icons.qrc b/src/icons/icons.qrc index 1ff153d1a..79bdffc17 100644 --- a/src/icons/icons.qrc +++ b/src/icons/icons.qrc @@ -52,6 +52,8 @@ images/clear_edit_16px@2x.png images/clearbreakpoints_16px.png images/clearbreakpoints_16px@2x.png + images/clone_16px.png + images/clone_16px@2x.png images/cm_add_24px.png images/cm_add_24px@2x.png images/cm_diff_24px.png diff --git a/src/icons/images/clone_16px.png b/src/icons/images/clone_16px.png new file mode 100644 index 0000000000000000000000000000000000000000..9cc80dafca4b3a574bb8326eda5241d9af4da530 GIT binary patch literal 411 zcmV;M0c8G(P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10VqjC zK~y-6jgvvj!Y~kpKW&?UH_|Q)^-@9+{(m8ODMcvlMZAKWW@yJv4KYP(!jLy&EO!C-sY;CvuR2^sXQwA~S zndHD=t@RT#0LGYbbJulv@5AzDvtb-ZhG8&3mIK}M`8)$v(=>a4s;a_*_nzDB7HU=H z@pyzfpVGXFh_-FJozhOp9#u71&a%t{!~7w2$K&w~RKPwaP}Ox^*Z;HD2y}s9uEbiq zeP{CaJe&KQ6Q`c?M<%i77z=_plPT@sIi6pAWB&D9zW|1Rw#^etyaE6K002ovPDHLk FV1i_-w`>3a literal 0 HcmV?d00001 diff --git a/src/icons/images/clone_16px@2x.png b/src/icons/images/clone_16px@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ac31d6e31712520d648df356ed7a1cdab0613080 GIT binary patch literal 673 zcmV;S0$%-zP)o z3eg}c7qhWboP9C&+OrE>KFI>S!|Z5w{@=0(hBQszX{|pAAwDKaa%YMWLc~du{FYKa zdY<=(%UQ(Z0*L4ffUae}U%v1Ea4CR=h#rV&qLg|DkeV)~)H4xHi0HvJzp#k81AxtD z^S&;iux31>CZy{h27|%b@A-WGx G;x^;n+`aW4ePzu`ZE{n+<`h4i$xy*qA2Qz zVYnNOM&AK^0MIy;Z_j2 zngB1;3Y>Yn6;Tv9#hX+R13(yt>`YurA$M%@ffaymrc)rB3H + + + + + + + + + + + + + + + + + + + + diff --git a/src/lay/lay/TechSetupDialog.ui b/src/lay/lay/TechSetupDialog.ui index 71b39a402..999b2810b 100644 --- a/src/lay/lay/TechSetupDialog.ui +++ b/src/lay/lay/TechSetupDialog.ui @@ -108,6 +108,9 @@ + + Add technology + ... @@ -116,12 +119,15 @@ :/add_16px.png:/add_16px.png - true + false + + Delete technology + ... @@ -130,33 +136,37 @@ :/clear_16px.png:/clear_16px.png - true + false - + + + Rename technology + + + Rename + + + + :/rename_16px@2x.png:/rename_16px@2x.png + + + + + Qt::Horizontal - - QSizePolicy::Expanding - - 141 + 40 20 - - - - Rename - - - diff --git a/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.h b/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.h index 422584323..451d2cf3f 100644 --- a/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.h +++ b/src/plugins/tools/net_tracer/db_plugin/dbNetTracerIO.h @@ -435,6 +435,16 @@ public: m_symbols.clear (); } + void clear_connections () + { + m_connections.clear (); + } + + void clear_symbols () + { + m_symbols.clear (); + } + void erase (iterator p) { m_connections.erase (p); @@ -524,7 +534,7 @@ public: const_iterator end () const { - return m_connectivity.begin (); + return m_connectivity.end (); } iterator begin () diff --git a/src/plugins/tools/net_tracer/db_plugin/dbNetTracerPlugin.cc b/src/plugins/tools/net_tracer/db_plugin/dbNetTracerPlugin.cc index ed67acd05..e9b442f7b 100644 --- a/src/plugins/tools/net_tracer/db_plugin/dbNetTracerPlugin.cc +++ b/src/plugins/tools/net_tracer/db_plugin/dbNetTracerPlugin.cc @@ -68,26 +68,94 @@ namespace tl namespace { -template -struct FallbackXMLWriteAdapator +static const db::NetTracerConnectivity * +get_default (const db::NetTracerTechnologyComponent &tc) { - FallbackXMLWriteAdapator (void (db::NetTracerConnectivity::*member) (const Value &)) - : mp_member (member) + for (auto d = tc.begin (); d != tc.end (); ++d) { + if (d->name ().empty ()) { + return d.operator-> (); + } + } + + if (tc.begin () != tc.end ()) { + return tc.begin ().operator-> (); + } else { + return 0; + } +} + +template +struct FallbackXMLWriteAdaptor +{ + FallbackXMLWriteAdaptor (void (db::NetTracerConnectivity::*member) (const Value &), void (db::NetTracerConnectivity::*clear) ()) + : mp_member (member), mp_clear (clear), mp_stack (0) { // .. nothing yet .. } void operator () (db::NetTracerTechnologyComponent &owner, tl::XMLReaderState &reader) const { - if (owner.size () == 0) { - owner.push_back (db::NetTracerConnectivity ()); + if (! mp_stack) { + mp_stack = const_cast (get_default (owner)); + if (! mp_stack) { + owner.push_back (db::NetTracerConnectivity ()); + mp_stack = (owner.end () - 1).operator-> (); + } + (mp_stack->*mp_clear) (); } + tl::XMLObjTag tag; - ((*owner.begin ()).*mp_member) (*reader.back (tag)); + (mp_stack->*mp_member) (*reader.back (tag)); } private: void (db::NetTracerConnectivity::*mp_member) (const Value &); + void (db::NetTracerConnectivity::*mp_clear) (); + mutable db::NetTracerConnectivity *mp_stack; +}; + +template +struct FallbackXMLReadAdaptor +{ + typedef tl::pass_by_ref_tag tag; + + FallbackXMLReadAdaptor (Iter (db::NetTracerConnectivity::*begin) () const, Iter (db::NetTracerConnectivity::*end) () const) + : mp_begin (begin), mp_end (end) + { + // .. nothing yet .. + } + + Value operator () () const + { + return *m_iter; + } + + bool at_end () const + { + return m_iter == m_end; + } + + void start (const db::NetTracerTechnologyComponent &parent) + { + const db::NetTracerConnectivity *tn = get_default (parent); + if (! tn) { + m_iter = Iter (); + m_end = Iter (); + } else { + m_iter = (tn->*mp_begin) (); + m_end = (tn->*mp_end) (); + } + } + + void next () + { + ++m_iter; + } + +private: + Iter (db::NetTracerConnectivity::*mp_begin) () const; + Iter (db::NetTracerConnectivity::*mp_end) () const; + Iter m_iter, m_end; }; } @@ -113,20 +181,20 @@ public: virtual tl::XMLElementBase *xml_element () const { return new db::TechnologyComponentXMLElement (net_tracer_component_name (), - // Fallback readers for migrating pre-0.28 setups to 0.28 - tl::XMLMember, FallbackXMLWriteAdapator , tl::XMLStdConverter > ( - tl::XMLMemberDummyReadAdaptor (), - FallbackXMLWriteAdapator (&NetTracerConnectivity::add), "connection") + - tl::XMLMember, FallbackXMLWriteAdapator , tl::XMLStdConverter > ( - tl::XMLMemberDummyReadAdaptor (), - FallbackXMLWriteAdapator (&NetTracerConnectivity::add_symbol), "symbols") + // 0.28 definitions - tl::make_element ((NetTracerTechnologyComponent::const_iterator (NetTracerTechnologyComponent::*) () const) &NetTracerTechnologyComponent::begin, (NetTracerTechnologyComponent::const_iterator (NetTracerTechnologyComponent::*) () const) &NetTracerTechnologyComponent::end, (void (NetTracerTechnologyComponent::*) (const NetTracerConnectivity &)) &NetTracerTechnologyComponent::push_back, "connectivity", + tl::make_element ((NetTracerTechnologyComponent::const_iterator (NetTracerTechnologyComponent::*) () const) &NetTracerTechnologyComponent::begin, (NetTracerTechnologyComponent::const_iterator (NetTracerTechnologyComponent::*) () const) &NetTracerTechnologyComponent::end, (void (NetTracerTechnologyComponent::*) (const NetTracerConnectivity &)) &NetTracerTechnologyComponent::push_back, "stack", tl::make_member (&NetTracerConnectivity::name, &NetTracerConnectivity::set_name, "name") + tl::make_member (&NetTracerConnectivity::description, &NetTracerConnectivity::set_description, "description") + tl::make_member ((NetTracerConnectivity::const_iterator (NetTracerConnectivity::*) () const) &NetTracerConnectivity::begin, (NetTracerConnectivity::const_iterator (NetTracerConnectivity::*) () const) &NetTracerConnectivity::end, &NetTracerConnectivity::add, "connection") + tl::make_member ((NetTracerConnectivity::const_symbol_iterator (NetTracerConnectivity::*) () const) &NetTracerConnectivity::begin_symbols, (NetTracerConnectivity::const_symbol_iterator (NetTracerConnectivity::*) () const) &NetTracerConnectivity::end_symbols, &NetTracerConnectivity::add_symbol, "symbols") - ) + ) + + // Fallback readers for migrating pre-0.28 setups to 0.28 and backward compatibility + tl::XMLMember, FallbackXMLWriteAdaptor , tl::XMLStdConverter > ( + FallbackXMLReadAdaptor (&NetTracerConnectivity::begin, &NetTracerConnectivity::end), + FallbackXMLWriteAdaptor (&NetTracerConnectivity::add, &NetTracerConnectivity::clear_connections), "connection") + + tl::XMLMember, FallbackXMLWriteAdaptor , tl::XMLStdConverter > ( + FallbackXMLReadAdaptor (&NetTracerConnectivity::begin_symbols, &NetTracerConnectivity::end_symbols), + FallbackXMLWriteAdaptor (&NetTracerConnectivity::add_symbol, &NetTracerConnectivity::clear_symbols), "symbols") ); } }; diff --git a/src/plugins/tools/net_tracer/lay_plugin/NetTracerTechComponentEditor.ui b/src/plugins/tools/net_tracer/lay_plugin/NetTracerTechComponentEditor.ui index 825513c9f..15e3a6e16 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/NetTracerTechComponentEditor.ui +++ b/src/plugins/tools/net_tracer/lay_plugin/NetTracerTechComponentEditor.ui @@ -20,6 +20,12 @@ Qt::Horizontal + + + 0 + 0 + + QFrame::NoFrame @@ -39,8 +45,11 @@ 0 - + + + Move selected stacks up + ... @@ -50,32 +59,24 @@ - - + + + + Add new stack + ... - :/del_16px.png:/del_16px.png + :/add_16px.png:/add_16px.png - Del + Return - - - - ... - - - - :/up_16px.png:/up_16px.png - - - - + QFrame::NoFrame @@ -85,19 +86,7 @@ - - - - - true - - - - Double-click to edit text - - - - + @@ -105,9 +94,18 @@ 1 + + Qt::ActionsContextMenu + + + QAbstractItemView::ExtendedSelection + false + + true + Name @@ -120,7 +118,50 @@ - + + + + Remove selected stacks + + + ... + + + + :/del_16px.png:/del_16px.png + + + Del + + + + + + + Move selected stacks down + + + ... + + + + :/up_16px.png:/up_16px.png + + + + + + + + true + + + + Double-click to edit text + + + + Qt::Horizontal @@ -133,24 +174,24 @@ - - + + + + Technology Stacks + + + + + + + Clone current stack + ... - :/add_16px.png:/add_16px.png - - - Return - - - - - - - Technology Stacks + :/clone_16px.png:/clone_16px.png diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc index 662f5e235..f2c19f968 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc @@ -135,8 +135,20 @@ NetTracerTechComponentEditor::NetTracerTechComponentEditor (QWidget *parent) { Ui::NetTracerTechComponentEditor::setupUi (this); + QAction *action; + action = new QAction (QObject::tr ("Add Stack"), this); + connect (action, SIGNAL (triggered ()), this, SLOT (add_clicked ())); + stack_tree->addAction (action); + action = new QAction (QObject::tr ("Delete Selected Stacks"), this); + connect (action, SIGNAL (triggered ()), this, SLOT (delete_clicked ())); + stack_tree->addAction (action); + action = new QAction (QObject::tr ("Duplicate Stack"), this); + connect (action, SIGNAL (triggered ()), this, SLOT (clone_clicked ())); + stack_tree->addAction (action); + connect (add_pb, SIGNAL (clicked ()), this, SLOT (add_clicked ())); connect (del_pb, SIGNAL (clicked ()), this, SLOT (del_clicked ())); + connect (clone_pb, SIGNAL (clicked ()), this, SLOT (clone_clicked ())); connect (move_up_pb, SIGNAL (clicked ()), this, SLOT (move_up_clicked ())); connect (move_down_pb, SIGNAL (clicked ()), this, SLOT (move_down_clicked ())); @@ -154,6 +166,7 @@ NetTracerTechComponentEditor::commit () return; } + commit_current (); *data = m_data; } @@ -175,6 +188,11 @@ NetTracerTechComponentEditor::setup () stack_tree->setItemDelegateForColumn (1, new NetTracerTechComponentColumnDelegate (stack_tree, &m_data)); update (); + + if (stack_tree->topLevelItemCount () > 0) { + stack_tree->setCurrentItem (stack_tree->topLevelItem (0)); + } + current_item_changed (stack_tree->currentItem (), 0); } void @@ -207,7 +225,47 @@ NetTracerTechComponentEditor::commit_current (QTreeWidgetItem *current) } } -void +static std::string +new_name (const db::NetTracerTechnologyComponent &data) +{ + for (int i = 1; ; ++i) { + std::string n = "STACK" + tl::to_string (i); + bool found = false; + for (auto d = data.begin (); d != data.end () && ! found; ++d) { + found = (d->name () == n); + } + if (! found) { + return n; + } + } + + return std::string (); +} + +void +NetTracerTechComponentEditor::clone_clicked () +{ + // removes focus from the tree view - commits the data + add_pb->setFocus (); + commit_current (); + + int row = stack_tree->currentItem () ? stack_tree->indexOfTopLevelItem (stack_tree->currentItem ()) : -1; + if (row < 0) { + m_data.push_back (db::NetTracerConnectivity ()); + row = int (m_data.size () - 1); + } else { + row += 1; + m_data.insert (m_data.begin () + row, db::NetTracerConnectivity ()); + m_data.begin ()[row] = m_data.begin ()[row - 1]; + } + + m_data.begin ()[row].set_name (new_name (m_data)); + + update (); + stack_tree->setCurrentItem (stack_tree->topLevelItem (row)); +} + +void NetTracerTechComponentEditor::add_clicked () { // removes focus from the tree view - commits the data @@ -223,6 +281,8 @@ NetTracerTechComponentEditor::add_clicked () m_data.insert (m_data.begin () + row, db::NetTracerConnectivity ()); } + m_data.begin ()[row].set_name (new_name (m_data)); + update (); stack_tree->setCurrentItem (stack_tree->topLevelItem (row)); } @@ -355,16 +415,12 @@ NetTracerTechComponentEditor::update () } else { item->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (name))); } - item->setData (1, Qt::DisplayRole, QVariant (tl::to_qstring (l->description ()))); - item->setData (0, Qt::UserRole, QVariant (n)); - } + item->setData (1, Qt::DisplayRole, QVariant (tl::to_qstring (l->description ()))); + item->setData (1, Qt::UserRole, QVariant (n)); - if (! stack_tree->currentItem () && stack_tree->topLevelItemCount () > 0) { - stack_tree->setCurrentItem (stack_tree->topLevelItem (0)); } - current_item_changed (stack_tree->currentItem (), 0); } } diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.h b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.h index db802fe6b..475b81e84 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.h +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.h @@ -62,6 +62,7 @@ public: public slots: void add_clicked (); + void clone_clicked (); void del_clicked (); void move_up_clicked (); void move_down_clicked (); From 7b4b345cf429ff25174fb87e32c6a500a5c282e3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 Sep 2022 19:10:30 +0200 Subject: [PATCH 08/54] Multiple stacks for net tracer - finished selection box in net tracer dialog --- .../laybasic/gsiDeclLayLayoutViewBase.cc | 8 + src/laybasic/laybasic/layLayoutViewBase.cc | 2 + src/laybasic/laybasic/layLayoutViewBase.h | 8 + .../net_tracer/lay_plugin/NetTracerDialog.ui | 155 ++++++++++-------- .../lay_plugin/layNetTracerDialog.cc | 86 +++++++++- .../lay_plugin/layNetTracerDialog.h | 5 + .../layNetTracerTechComponentEditor.cc | 13 +- 7 files changed, 194 insertions(+), 83 deletions(-) diff --git a/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc b/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc index dc10d5ba7..cef7e30bf 100644 --- a/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc +++ b/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc @@ -1631,6 +1631,14 @@ LAYBASIC_PUBLIC Class decl_LayoutViewBase ("lay", "LayoutVi "Before version 0.25 this event was based on the observer pattern obsolete now. The corresponding methods " "(add_cellview_observer/remove_cellview_observer) have been removed in 0.25.\n" ) + + gsi::event ("on_apply_technology", static_cast (lay::LayoutViewBase::*)> (&lay::LayoutViewBase::apply_technology_event), gsi::arg ("cellview_index"), + "@brief An event indicating that a cellview has requested a new technology\n" + "\n" + "If the technology of a cellview is changed, this event is triggered.\n" + "The integer parameter of this event will indicate the cellview that has changed.\n" + "\n" + "This event has been introduced in version 0.28.\n" + ) + gsi::event ("on_file_open", static_cast (&lay::LayoutViewBase::file_open_event), "@brief An event indicating that a file was opened\n" "\n" diff --git a/src/laybasic/laybasic/layLayoutViewBase.cc b/src/laybasic/laybasic/layLayoutViewBase.cc index 57c00aa38..e2ec59bce 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.cc +++ b/src/laybasic/laybasic/layLayoutViewBase.cc @@ -2314,6 +2314,8 @@ LayoutViewBase::signal_apply_technology (lay::LayoutHandle *layout_handle) } + apply_technology_event (int (i)); + } } diff --git a/src/laybasic/laybasic/layLayoutViewBase.h b/src/laybasic/laybasic/layLayoutViewBase.h index 5837c042d..d34426ce8 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.h +++ b/src/laybasic/laybasic/layLayoutViewBase.h @@ -677,6 +677,14 @@ public: */ tl::event cellview_changed_event; + /** + * @brief A event signalling that one cellview has requested a new technology + * + * This event is triggered if a cellview has requested a new technology. + * The argument is the index of the cellview that received the new technology. + */ + tl::event apply_technology_event; + /** * @brief An event signalling that a file has been loaded. * diff --git a/src/plugins/tools/net_tracer/lay_plugin/NetTracerDialog.ui b/src/plugins/tools/net_tracer/lay_plugin/NetTracerDialog.ui index 823a74505..8a2669a57 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/NetTracerDialog.ui +++ b/src/plugins/tools/net_tracer/lay_plugin/NetTracerDialog.ui @@ -29,24 +29,14 @@ 6 - + Qt::Horizontal - - - - Configure - - - false - - - - + @@ -67,7 +57,7 @@ Select one or multiple nets and choose "Export" to export the selected - + @@ -196,60 +186,7 @@ Select one or multiple nets and choose "Export" to export the selected - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 5 - 20 - - - - - - - - Trace depth: - - - - - - - Close - - - - - - - - 1 - 0 - - - - - - - - - - - Layer Stack - - - false - - - - + @@ -332,7 +269,6 @@ Select one or multiple nets and choose "Export" to export the selected 12 - 75 true @@ -477,7 +413,23 @@ p, li { white-space: pre-wrap; } - + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 5 + 20 + + + + + @@ -488,7 +440,6 @@ p, li { white-space: pre-wrap; } 12 - 75 false true @@ -498,14 +449,54 @@ p, li { white-space: pre-wrap; } - + + + + + 0 + 0 + + + + QComboBox::AdjustToContents + + + + + + + Layer Stack + + + false + + + + + + + Configure + + + false + + + + + + + Close + + + + shapes - + @@ -521,6 +512,26 @@ p, li { white-space: pre-wrap; } + + + + Trace depth: + + + + + + + + 1 + 0 + + + + + + + diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.cc b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.cc index 454f58330..654212b30 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.cc +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.cc @@ -92,7 +92,9 @@ NetTracerDialog::NetTracerDialog (lay::Dispatcher *root, LayoutViewBase *view) view->layer_list_changed_event.add (this, &NetTracerDialog::layer_list_changed); + attach_events (); update_info (); + update_list_of_stacks (); } NetTracerDialog::~NetTracerDialog () @@ -101,6 +103,69 @@ NetTracerDialog::~NetTracerDialog () clear_nets (); } +void +NetTracerDialog::attach_events () +{ + detach_from_all_events (); + + mp_view->layer_list_changed_event.add (this, &NetTracerDialog::layer_list_changed); + + db::Technologies::instance ()->technology_changed_event.add (this, &NetTracerDialog::update_list_of_stacks_with_technology); + db::Technologies::instance ()->technologies_changed_event.add (this, &NetTracerDialog::update_list_of_stacks); + + mp_view->cellviews_changed_event.add (this, &NetTracerDialog::update_list_of_stacks); + mp_view->apply_technology_event.add (this, &NetTracerDialog::update_list_of_stacks_with_cellview); +} + +void +NetTracerDialog::update_list_of_stacks_with_technology (db::Technology *) +{ + update_list_of_stacks (); +} + +void +NetTracerDialog::update_list_of_stacks_with_cellview (int) +{ + update_list_of_stacks (); +} + +void +NetTracerDialog::update_list_of_stacks () +{ + QString current_name = stack_selector->currentText (); + + std::set names; + for (unsigned int cvi = 0; cvi < mp_view->cellviews (); ++cvi) { + const db::Technology *tech = mp_view->cellview (cvi)->technology (); + if (tech) { + const db::NetTracerTechnologyComponent *tech_component = dynamic_cast (tech->component_by_name (db::net_tracer_component_name ())); + if (tech_component) { + for (auto d = tech_component->begin (); d != tech_component->end (); ++d) { + names.insert (tl::to_qstring (d->name ())); + } + } + } + } + + stack_selector->clear (); + + int current_index = 0; + int i = 0; + for (auto n = names.begin (); n != names.end (); ++n, ++i) { + if (n->isEmpty ()) { + stack_selector->addItem (tr ("(default)"), QVariant (*n)); + } else { + stack_selector->addItem (*n, QVariant (*n)); + } + if (*n == current_name) { + current_index = i; + } + } + + stack_selector->setVisible (stack_selector->count () >= 2); + stack_selector->setCurrentIndex (current_index); +} + void NetTracerDialog::clear_nets () { @@ -290,14 +355,27 @@ NetTracerDialog::get_net_tracer_setup (const lay::CellView &cv, db::NetTracerDat if (! tech) { return false; } - const db::NetTracerConnectivity *tech_component = dynamic_cast (tech->component_by_name (db::net_tracer_component_name ())); + + const db::NetTracerTechnologyComponent *tech_component = dynamic_cast (tech->component_by_name (db::net_tracer_component_name ())); if (! tech_component) { return false; } - // Set up the net tracer environment - data = tech_component->get_tracer_data (cv->layout ()); + std::string stack_name = tl::to_string (stack_selector->itemData (stack_selector->currentIndex ()).toString ()); + const db::NetTracerConnectivity *connectivity = 0; + for (auto d = tech_component->begin (); d != tech_component->end () && ! connectivity; ++d) { + if (d->name () == stack_name) { + connectivity = d.operator-> (); + } + } + + if (! connectivity) { + return false; + } + + // Set up the net tracer environment + data = connectivity->get_tracer_data (cv->layout ()); return true; } @@ -545,6 +623,7 @@ NetTracerDialog::configure (const std::string &name, const std::string &value) update_highlights (); adjust_view (); update_info (); + update_list_of_stacks (); } return taken; @@ -1243,6 +1322,7 @@ BEGIN_PROTECTED lay::TechComponentSetupDialog dialog (this, &tech, db::net_tracer_component_name ()); if (dialog.exec ()) { *db::Technologies::instance ()->technology_by_name (tech.name ()) = tech; + update_list_of_stacks (); } END_PROTECTED diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.h b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.h index b777d32e7..f439f4af6 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.h +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerDialog.h @@ -112,6 +112,11 @@ private: void commit (); size_t get_trace_depth (); + void attach_events (); + void update_list_of_stacks_with_technology (db::Technology *); + void update_list_of_stacks_with_cellview (int); + void update_list_of_stacks (); + void update_highlights (); void adjust_view (); void clear_markers (); diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc index f2c19f968..22f69020f 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.cc @@ -300,8 +300,6 @@ NetTracerTechComponentEditor::del_clicked () selected_rows.insert (i->row ()); } - stack_tree->setCurrentIndex (QModelIndex ()); - int offset = 0; for (std::set::const_iterator r = selected_rows.begin (); r != selected_rows.end (); ++r) { m_data.erase (m_data.begin () + (*r - offset)); @@ -309,6 +307,7 @@ NetTracerTechComponentEditor::del_clicked () } update (); + stack_tree->setCurrentItem (0); } void @@ -345,11 +344,10 @@ NetTracerTechComponentEditor::move_up_clicked () // select the new items for (std::set ::const_iterator s = selected_rows.begin (); s != selected_rows.end (); ++s) { - stack_tree->selectionModel ()->select (stack_tree->model ()->index (*s, 0), QItemSelectionModel::Select | QItemSelectionModel::Rows); + stack_tree->topLevelItem (*s)->setSelected (true); } - if (n_current >= 0) { - stack_tree->selectionModel ()->select (stack_tree->model ()->index (n_current, 0), QItemSelectionModel::Current | QItemSelectionModel::Rows); + stack_tree->setCurrentItem (stack_tree->topLevelItem (n_current), 0, QItemSelectionModel::Current); } } @@ -389,11 +387,10 @@ NetTracerTechComponentEditor::move_down_clicked () // select the new items for (std::set ::const_iterator s = selected_rows.begin (); s != selected_rows.end (); ++s) { - stack_tree->selectionModel ()->select (stack_tree->model ()->index (*s, 0), QItemSelectionModel::Select | QItemSelectionModel::Rows); + stack_tree->topLevelItem (*s)->setSelected (true); } - if (n_current >= 0) { - stack_tree->selectionModel ()->select (stack_tree->model ()->index (n_current, 0), QItemSelectionModel::Current | QItemSelectionModel::Rows); + stack_tree->setCurrentItem (stack_tree->topLevelItem (n_current), 0, QItemSelectionModel::Current); } } From d1b7cd1f8f61894971e3d041ca5f3ed3f12f2e64 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 26 Sep 2022 23:37:22 +0200 Subject: [PATCH 09/54] WIP: first steps towards multi-segment rulers --- src/ant/ant/RulerPropertiesPage.ui | 1154 ++++++++++++++++------------ src/ant/ant/antObject.cc | 288 +++++-- src/ant/ant/antObject.h | 128 ++- src/ant/ant/antPropertiesPage.cc | 194 ++++- src/ant/ant/antPropertiesPage.h | 10 +- src/ant/ant/antService.cc | 74 +- src/ant/ant/gsiDeclAnt.cc | 85 +- 7 files changed, 1280 insertions(+), 653 deletions(-) diff --git a/src/ant/ant/RulerPropertiesPage.ui b/src/ant/ant/RulerPropertiesPage.ui index 5e4497bbd..567287ddc 100644 --- a/src/ant/ant/RulerPropertiesPage.ui +++ b/src/ant/ant/RulerPropertiesPage.ui @@ -13,10 +13,7 @@ Form - - - 6 - + 9 @@ -29,7 +26,7 @@ 9 - + QFrame::NoFrame @@ -53,13 +50,95 @@ 6 - - + + - Second point (x/y) + X label format + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + + + + + + + Label format + + + + + + + Y label format + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + @@ -67,173 +146,31 @@ - - + + - <html>(See <a href="int:/manual/ruler_properties.xml">here</a> for a description of the properties)</html> + Outline - - + + + + + ... position - - - - Qt::Vertical - - - - 20 - 5 - - - - - - - - - - - - Diagonal - - - - - Horizonal and vertical (in this order) - - - - - Diagonal plus horizonal and vertical (triangle) - - - - - Vertical and horizonal (in this order) - - - - - Diagonal plus vertical and horizontal (triangle) - - - - - Box - - - - - Ellipse - - - - - - + + - d = + ... position - - - - Qt::Vertical - - - - 20 - 5 - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 456 - 16 - - - - - - - - - Ruler - - - - - Arrow at end - - - - - Arrow at start - - - - - Arrow at both ends - - - - - Plain line - - - - - Cross at end - - - - - Cross at start - - - - - Cross at both ends - - - - - - - - - 1 - 0 - - - - - - - - Delta (x/y) - - - - + QFrame::NoFrame @@ -378,42 +315,176 @@ - - + + + + + Diagonal + + + + + Horizonal and vertical (in this order) + + + + + Diagonal plus horizonal and vertical (triangle) + + + + + Vertical and horizonal (in this order) + + + + + Diagonal plus vertical and horizontal (triangle) + + + + + Box + + + + + Ellipse + + + + + + + + + Ruler + + + + + Arrow at end + + + + + Arrow at start + + + + + Arrow at both ends + + + + + Plain line + + + + + Cross at end + + + + + Cross at start + + + + + Cross at both ends + + + + + + + + <html>(See <a href="int:/manual/ruler_properties.xml">here</a> for a description of the properties)</html> + + + + + + + + 0 + 0 + + + + + Sans Serif + 12 + false + true + false + false + + + + Ruler Properties + + + + + - Qt::Horizontal + Qt::Vertical + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + - - - - x = - - - - - - - Label format - - - - - - - Length - - - - - - - Outline - - - - + QFrame::NoFrame @@ -524,210 +595,7 @@ - - - - - 1 - 0 - - - - true - - - - - - - - 1 - 0 - - - - true - - - - - - - First point (x/y) - - - - - - - - 1 - 0 - - - - - - - - y = - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Swap points - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 20 - - - - - - - - Snap to layout: - - - - - - - P1 - - - false - - - - - - - P2 - - - false - - - - - - - Both (auto-measure) - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - Qt::Vertical - - - - 20 - 5 - - - - - - - - y = - - - - - - - - - - - 1 - 0 - - - - true - - - - - - - - 1 - 0 - - - - - - - - ... position - - - - - - - + QFrame::NoFrame @@ -838,98 +706,429 @@ - - + + ... position - - - - x = - - - - - + + - - 1 - 0 - - - - - - - - Y label format - - - - - - - X label format - - - - - - - y = - - - - - - - x = - - - - - - - + 0 - 0 + 1 - - - Sans Serif - 12 - 75 - false - true - false - false - + + + 0 + 100 + - - Ruler Properties + + 1 + + + Single + + + + + + First point (x/y) + + + + + + + x = + + + + + + + + 1 + 0 + + + + + + + + y = + + + + + + + + 1 + 0 + + + + + + + + Qt::Vertical + + + + 558 + 157 + + + + + + + + + Line + + + + + + y = + + + + + + + First point (x/y) + + + + + + + d = + + + + + + + y = + + + + + + + y = + + + + + + + + 1 + 0 + + + + true + + + + + + + Length + + + + + + + x = + + + + + + + Delta (x/y) + + + + + + + + 1 + 0 + + + + + + + + + 1 + 0 + + + + + + + + + 1 + 0 + + + + + + + + Second point (x/y) + + + + + + + + 1 + 0 + + + + + + + + x = + + + + + + + + 1 + 0 + + + + true + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + + x = + + + + + + + + 1 + 0 + + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 30 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Swap points + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 5 + 20 + + + + + + + + Snap to layout + + + + + + + P1 + + + false + + + + + + + P2 + + + false + + + + + + + Both (auto-measure) + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Multi-Segment + + + + + + false + + + true + + + + x + + + + + y + + + + + + + + + Text edit + + + + + + false + + + + + - - - - Qt::Vertical - - - - 20 - 0 - - - - @@ -937,13 +1136,6 @@ fmt_y_le style_cb outline_cb - x1 - y1 - x2 - y2 - dx - dy - dd diff --git a/src/ant/ant/antObject.cc b/src/ant/ant/antObject.cc index 43bdf736b..5603de0d8 100644 --- a/src/ant/ant/antObject.cc +++ b/src/ant/ant/antObject.cc @@ -33,7 +33,7 @@ namespace ant { Object::Object () - : m_p1 (), m_p2 (), m_id (-1), + : m_id (-1), m_fmt_x ("$X"), m_fmt_y ("$Y"), m_fmt ("$D"), m_style (STY_ruler), m_outline (OL_diag), m_snap (true), m_angle_constraint (lay::AC_Global), @@ -45,8 +45,8 @@ Object::Object () // .. nothing yet .. } -Object::Object (const db::DPoint &p1, const db::DPoint &p2, int id, const std::string &fmt_x, const std::string &fmt_y, const std::string &fmt, style_type style, outline_type outline, bool snap, lay::angle_constraint_type angle_constraint) - : m_p1 (p1), m_p2 (p2), m_id (id), +Object::Object (const db::DPoint &_p1, const db::DPoint &_p2, int id, const std::string &fmt_x, const std::string &fmt_y, const std::string &fmt, style_type style, outline_type outline, bool snap, lay::angle_constraint_type angle_constraint) + : m_id (id), m_fmt_x (fmt_x), m_fmt_y (fmt_y), m_fmt (fmt), m_style (style), m_outline (outline), m_snap (snap), m_angle_constraint (angle_constraint), @@ -55,11 +55,25 @@ Object::Object (const db::DPoint &p1, const db::DPoint &p2, int id, const std::s m_xlabel_xalign (AL_auto), m_xlabel_yalign (AL_auto), m_ylabel_xalign (AL_auto), m_ylabel_yalign (AL_auto) { - // .. nothing else .. + p1 (_p1); + p2 (_p2); } -Object::Object (const db::DPoint &p1, const db::DPoint &p2, int id, const ant::Template &t) - : m_p1 (p1), m_p2 (p2), m_id (id), +Object::Object (const Object::point_list &pts, int id, const std::string &fmt_x, const std::string &fmt_y, const std::string &fmt, style_type style, outline_type outline, bool snap, lay::angle_constraint_type angle_constraint) + : m_id (id), + m_fmt_x (fmt_x), m_fmt_y (fmt_y), m_fmt (fmt), + m_style (style), m_outline (outline), + m_snap (snap), m_angle_constraint (angle_constraint), + m_main_position (POS_auto), + m_main_xalign (AL_auto), m_main_yalign (AL_auto), + m_xlabel_xalign (AL_auto), m_xlabel_yalign (AL_auto), + m_ylabel_xalign (AL_auto), m_ylabel_yalign (AL_auto) +{ + set_points (pts); +} + +Object::Object (const db::DPoint &_p1, const db::DPoint &_p2, int id, const ant::Template &t) + : m_id (id), m_fmt_x (t.fmt_x ()), m_fmt_y (t.fmt_y ()), m_fmt (t.fmt ()), m_style (t.style ()), m_outline (t.outline ()), m_snap (t.snap ()), m_angle_constraint (t.angle_constraint ()), @@ -69,11 +83,26 @@ Object::Object (const db::DPoint &p1, const db::DPoint &p2, int id, const ant::T m_xlabel_xalign (t.xlabel_xalign ()), m_xlabel_yalign (t.xlabel_yalign ()), m_ylabel_xalign (t.ylabel_xalign ()), m_ylabel_yalign (t.ylabel_yalign ()) { - // .. nothing else .. + p1 (_p1); + p2 (_p2); +} + +Object::Object (const Object::point_list &pts, int id, const ant::Template &t) + : m_id (id), + m_fmt_x (t.fmt_x ()), m_fmt_y (t.fmt_y ()), m_fmt (t.fmt ()), + m_style (t.style ()), m_outline (t.outline ()), + m_snap (t.snap ()), m_angle_constraint (t.angle_constraint ()), + m_category (t.category ()), + m_main_position (t.main_position ()), + m_main_xalign (t.main_xalign ()), m_main_yalign (t.main_yalign ()), + m_xlabel_xalign (t.xlabel_xalign ()), m_xlabel_yalign (t.xlabel_yalign ()), + m_ylabel_xalign (t.ylabel_xalign ()), m_ylabel_yalign (t.ylabel_yalign ()) +{ + set_points (pts); } Object::Object (const ant::Object &d) - : m_p1 (d.m_p1), m_p2 (d.m_p2), m_id (d.m_id), + : m_points (d.m_points), m_id (d.m_id), m_fmt_x (d.m_fmt_x), m_fmt_y (d.m_fmt_y), m_fmt (d.m_fmt), m_style (d.m_style), m_outline (d.m_outline), m_snap (d.m_snap), m_angle_constraint (d.m_angle_constraint), @@ -90,8 +119,7 @@ Object & Object::operator= (const ant::Object &d) { if (this != &d) { - m_p1 = d.m_p1; - m_p2 = d.m_p2; + m_points = d.m_points; m_id = d.m_id; m_fmt_x = d.m_fmt_x; m_fmt_y = d.m_fmt_y; @@ -119,11 +147,8 @@ Object::operator< (const ant::Object &b) const if (m_id != b.m_id) { return m_id < b.m_id; } - if (m_p1 != b.m_p1) { - return m_p1 < b.m_p1; - } - if (m_p2 != b.m_p2) { - return m_p2 < b.m_p2; + if (m_points != b.m_points) { + return m_points < b.m_points; } if (m_fmt_x != b.m_fmt_x) { return m_fmt_x < b.m_fmt_x; @@ -187,7 +212,7 @@ Object::equals (const db::DUserObjectBase *d) const bool Object::operator== (const ant::Object &d) const { - return m_p1 == d.m_p1 && m_p2 == d.m_p2 && m_id == d.m_id && + return m_points == d.m_points && m_id == d.m_id && m_fmt_x == d.m_fmt_x && m_fmt_y == d.m_fmt_y && m_fmt == d.m_fmt && m_style == d.m_style && m_outline == d.m_outline && m_snap == d.m_snap && m_angle_constraint == d.m_angle_constraint && @@ -199,6 +224,87 @@ Object::operator== (const ant::Object &d) const ; } +void +Object::set_points (const point_list &points) +{ + point_list new_points; + auto p = points.begin (); + while (p != points.end ()) { + auto pp = p + 1; + while (pp != points.end () && *pp == *p) { + ++pp; + } + new_points.push_back (*p); + p = pp; + } + + if (m_points != new_points) { + m_points = new_points; + property_changed (); + } +} + +db::DPoint +Object::seg_p1 (size_t seg_index) const +{ + if (seg_index < m_points.size ()) { + return m_points[seg_index]; + } else if (m_points.empty ()) { + return db::DPoint (); + } else { + return m_points.back (); + } +} + +db::DPoint +Object::seg_p2 (size_t seg_index) const +{ + if (seg_index + 1 < m_points.size ()) { + return m_points[seg_index + 1]; + } else if (m_points.empty ()) { + return db::DPoint (); + } else { + return m_points.back (); + } +} + +void +Object::p1 (const db::DPoint &p) +{ + if (! p1 ().equal (p)) { + if (m_points.size () < 1) { + m_points.push_back (p); + } else { + m_points.front () = p; + // makes sure there is only one point if p1 == p2 + if (m_points.size () == 2 && m_points.back () == m_points.front ()) { + m_points.pop_back (); + } + } + property_changed (); + } +} + +void +Object::p2 (const db::DPoint &p) +{ + if (! p2 ().equal (p)) { + if (m_points.size () < 2) { + if (m_points.empty ()) { + m_points.push_back (db::DPoint ()); + } + m_points.push_back (p); + } else { + m_points.back () = p; + } + // makes sure there is only one point if p1 == p2 + if (m_points.size () == 2 && m_points.back () == m_points.front ()) { + m_points.pop_back (); + } + property_changed (); + } +} + bool Object::less (const db::DUserObjectBase *d) const { @@ -226,7 +332,11 @@ Object::clone () const db::DBox Object::box () const { - return db::DBox (m_p1, m_p2); + db::DBox bx; + for (auto d = m_points.begin (); d != m_points.end (); ++d) { + bx += *d; + } + return bx; } class AnnotationEval @@ -245,38 +355,12 @@ private: db::DFTrans m_trans; }; -static double -delta_x (const Object &obj, const db::DFTrans &t) -{ - double dx = ((t * obj.p2 ()).x () - (t * obj.p1 ()).x ()); - - // avoid "almost 0" outputs - if (fabs (dx) < 1e-5 /*micron*/) { - dx = 0; - } - - return dx; -} - -static double -delta_y (const Object &obj, const db::DFTrans &t) -{ - double dy = ((t * obj.p2 ()).y () - (t * obj.p1 ()).y ()); - - // avoid "almost 0" outputs - if (fabs (dy) < 1e-5 /*micron*/) { - dy = 0; - } - - return dy; -} - class AnnotationEvalFunction : public tl::EvalFunction { public: - AnnotationEvalFunction (char function, const AnnotationEval *eval) - : m_function (function), mp_eval (eval) + AnnotationEvalFunction (char function, const AnnotationEval *eval, size_t index) + : m_function (function), mp_eval (eval), m_index (index) { // .. nothing yet .. } @@ -301,36 +385,73 @@ public: } else if (m_function == 'Y') { out = delta_y (obj, trans); } else if (m_function == 'U') { - out = (trans * obj.p1 ()).x (); + out = (trans * p1 (obj)).x (); } else if (m_function == 'V') { - out = (trans * obj.p1 ()).y (); + out = (trans * p1 (obj)).y (); } else if (m_function == 'P') { - out = (trans * obj.p2 ()).x (); + out = (trans * p2 (obj)).x (); } else if (m_function == 'Q') { - out = (trans * obj.p2 ()).y (); + out = (trans * p2 (obj)).y (); } else { out = tl::Variant (); } } + db::DPoint p1 (const Object &obj) const + { + return obj.seg_p1 (m_index); + } + + db::DPoint p2 (const Object &obj) const + { + return obj.seg_p2 (m_index); + } + + double + delta_x (const Object &obj, const db::DFTrans &t) const + { + double dx = ((t * p2 (obj)).x () - (t * p1 (obj)).x ()); + + // avoid "almost 0" outputs + if (fabs (dx) < 1e-5 /*micron*/) { + dx = 0; + } + + return dx; + } + + double + delta_y (const Object &obj, const db::DFTrans &t) const + { + double dy = ((t * p2 (obj)).y () - (t * p1 (obj)).y ()); + + // avoid "almost 0" outputs + if (fabs (dy) < 1e-5 /*micron*/) { + dy = 0; + } + + return dy; + } + private: char m_function; const AnnotationEval *mp_eval; + size_t m_index; }; std::string -Object::formatted (const std::string &fmt, const db::DFTrans &t) const +Object::formatted (const std::string &fmt, const db::DFTrans &t, size_t index) const { AnnotationEval eval (*this, t); - eval.define_function ("L", new AnnotationEvalFunction('L', &eval)); // manhattan length - eval.define_function ("D", new AnnotationEvalFunction('D', &eval)); // euclidian distance - eval.define_function ("X", new AnnotationEvalFunction('X', &eval)); // x delta - eval.define_function ("Y", new AnnotationEvalFunction('Y', &eval)); // y delta - eval.define_function ("U", new AnnotationEvalFunction('U', &eval)); // p1.x - eval.define_function ("V", new AnnotationEvalFunction('V', &eval)); // p1.y - eval.define_function ("P", new AnnotationEvalFunction('P', &eval)); // p2.x - eval.define_function ("Q", new AnnotationEvalFunction('Q', &eval)); // p2.y - eval.define_function ("A", new AnnotationEvalFunction('A', &eval)); // area mm2 + eval.define_function ("L", new AnnotationEvalFunction('L', &eval, index)); // manhattan length + eval.define_function ("D", new AnnotationEvalFunction('D', &eval, index)); // euclidian distance + eval.define_function ("X", new AnnotationEvalFunction('X', &eval, index)); // x delta + eval.define_function ("Y", new AnnotationEvalFunction('Y', &eval, index)); // y delta + eval.define_function ("U", new AnnotationEvalFunction('U', &eval, index)); // p1.x + eval.define_function ("V", new AnnotationEvalFunction('V', &eval, index)); // p1.y + eval.define_function ("P", new AnnotationEvalFunction('P', &eval, index)); // p2.x + eval.define_function ("Q", new AnnotationEvalFunction('Q', &eval, index)); // p2.y + eval.define_function ("A", new AnnotationEvalFunction('A', &eval, index)); // area mm2 return eval.interpolate (fmt); } @@ -343,6 +464,9 @@ Object::class_name () const void Object::from_string (const char *s, const char * /*base_dir*/) { + m_points.clear (); + point_list new_points; + tl::Extractor ex (s); while (! ex.at_end ()) { @@ -408,6 +532,14 @@ Object::from_string (const char *s, const char * /*base_dir*/) p.set_y (q); p2 (p); + } else if (ex.test ("pt=")) { + + double x = 0.0, y = 0.0; + ex.read (x); + ex.expect (":"); + ex.read (y); + new_points.push_back (db::DPoint (x, y)); + } else if (ex.test ("position=")) { std::string s; @@ -518,6 +650,10 @@ Object::from_string (const char *s, const char * /*base_dir*/) ex.test (","); } + + if (! new_points.empty ()) { + set_points (new_points); + } } std::string @@ -529,18 +665,28 @@ Object::to_string () const r += tl::to_string (id ()); r += ","; - r += "x1="; - r += tl::to_string (p1 ().x ()); - r += ","; - r += "y1="; - r += tl::to_string (p1 ().y ()); - r += ","; - r += "x2="; - r += tl::to_string (p2 ().x ()); - r += ","; - r += "y2="; - r += tl::to_string (p2 ().y ()); - r += ","; + if (m_points.size () > 2) { + for (auto p = m_points.begin (); p != m_points.end (); ++p) { + r += "pt="; + r += tl::to_string (p->x ()); + r += ":"; + r += tl::to_string (p->y ()); + r += ","; + } + } else { + r += "x1="; + r += tl::to_string (p1 ().x ()); + r += ","; + r += "y1="; + r += tl::to_string (p1 ().y ()); + r += ","; + r += "x2="; + r += tl::to_string (p2 ().x ()); + r += ","; + r += "y2="; + r += tl::to_string (p2 ().y ()); + r += ","; + } r += "category="; r += tl::to_word_or_quoted_string (category ()); diff --git a/src/ant/ant/antObject.h b/src/ant/ant/antObject.h index 3fe1422a8..6358417c0 100644 --- a/src/ant/ant/antObject.h +++ b/src/ant/ant/antObject.h @@ -50,6 +50,7 @@ class ANT_PUBLIC Object { public: typedef db::coord_traits coord_traits; + typedef std::vector point_list; /** * @brief The ruler style @@ -109,11 +110,21 @@ public: */ Object (const db::DPoint &p1, const db::DPoint &p2, int id, const std::string &fmt_x, const std::string &fmt_y, const std::string &fmt, style_type style, outline_type outline, bool snap, lay::angle_constraint_type angle_constraint); + /** + * @brief Parametrized constructor and a list of points + */ + Object (const point_list &points, int id, const std::string &fmt_x, const std::string &fmt_y, const std::string &fmt, style_type style, outline_type outline, bool snap, lay::angle_constraint_type angle_constraint); + /** * @brief Parametrized constructor from a template */ Object (const db::DPoint &p1, const db::DPoint &p2, int id, const ant::Template &d); + /** + * @brief Parametrized constructor from a template and a list of points + */ + Object (const point_list &points, int id, const ant::Template &d); + /** * @brief Copy constructor */ @@ -185,8 +196,9 @@ public: */ virtual void transform (const db::DCplxTrans &t) { - m_p1 = t * m_p1; - m_p2 = t * m_p2; + for (auto p = m_points.begin (); p != m_points.end (); ++p) { + *p = t * *p; + } property_changed (); } @@ -195,8 +207,9 @@ public: */ virtual void transform (const db::DTrans &t) { - m_p1 = t * m_p1; - m_p2 = t * m_p2; + for (auto p = m_points.begin (); p != m_points.end (); ++p) { + *p = t * *p; + } property_changed (); } @@ -205,8 +218,9 @@ public: */ virtual void transform (const db::DFTrans &t) { - m_p1 = t * m_p1; - m_p2 = t * m_p2; + for (auto p = m_points.begin (); p != m_points.end (); ++p) { + *p = t * *p; + } property_changed (); } @@ -224,10 +238,11 @@ public: /** * @brief Moves the object by the given distance */ - Object &move (const db::DVector &p) + Object &move (const db::DVector &d) { - m_p1 += p; - m_p2 += p; + for (auto p = m_points.begin (); p != m_points.end (); ++p) { + *p += d; + } return *this; } @@ -265,42 +280,71 @@ public: } /** - * @brief Gets the first definition point + * @brief Gets the ruler's definition points */ - const db::DPoint &p1 () const + const point_list &points () const { - return m_p1; + return m_points; + } + + /** + * @brief Sets the ruler's definition points + */ + void set_points (const point_list &points); + + /** + * @brief Gets the first point of the indicated segment + */ + db::DPoint seg_p1 (size_t seg_index) const; + + /** + * @brief Gets the second point of the indicated segment + */ + db::DPoint seg_p2 (size_t seg_index) const; + + /** + * @brief Gets the number of segments + * + * The number of segments is at least 1 for backward compatibility. + */ + size_t segments () const + { + return m_points.size () < 2 ? 1 : m_points.size () - 1; + } + + /** + * @brief Gets the first definition point + * + * This method is provided for backward compatibility. Use the point list accessor for generic point retrieval. + */ + db::DPoint p1 () const + { + return seg_p1 (0); } /** * @brief Gets the second definition point + * + * This method is provided for backward compatibility. Use the point list accessor for generic point retrieval. */ - const db::DPoint &p2 () const + db::DPoint p2 () const { - return m_p2; + return seg_p2 (0); } /** * @brief Sets the first definition point + * + * This method is provided for backward compatibility. Use the point list accessor for generic point retrieval. */ - void p1 (const db::DPoint &p) - { - if (!m_p1.equal (p)) { - m_p1 = p; - property_changed (); - } - } + void p1 (const db::DPoint &p); /** * @brief Sets the second definition point + * + * This method is provided for backward compatibility. Use the point list accessor for generic point retrieval. */ - void p2 (const db::DPoint &p) - { - if (!m_p2.equal (p)) { - m_p2 = p; - property_changed (); - } - } + void p2 (const db::DPoint &p); /** * @brief Gets the ID of the annotation object @@ -619,52 +663,52 @@ public: /** * @brief Gets the formatted text for the x label */ - std::string text_x () const + std::string text_x (size_t index) const { - return formatted (m_fmt_x, db::DFTrans ()); + return formatted (m_fmt_x, db::DFTrans (), index); } /** * @brief Gets the formatted text for the y label */ - std::string text_y () const + std::string text_y (size_t index) const { - return formatted (m_fmt_y, db::DFTrans ()); + return formatted (m_fmt_y, db::DFTrans (), index); } /** * @brief Gets the formatted text for the main label */ - std::string text () const + std::string text (size_t index) const { - return formatted (m_fmt, db::DFTrans ()); + return formatted (m_fmt, db::DFTrans (), index); } /** * @brief Gets the formatted text for the x label * @param t The transformation to apply to the vector before producing the text */ - std::string text_x (const db::DFTrans &t) const + std::string text_x (size_t index, const db::DFTrans &t) const { - return formatted (m_fmt_x, t); + return formatted (m_fmt_x, t, index); } /** * @brief Gets the formatted text for the y label * @param t The transformation to apply to the vector before producing the text */ - std::string text_y (const db::DFTrans &t) const + std::string text_y (size_t index, const db::DFTrans &t) const { - return formatted (m_fmt_y, t); + return formatted (m_fmt_y, t, index); } /** * @brief Gets the formatted text for the main label * @param t The transformation to apply to the vector before producing the text */ - std::string text (const db::DFTrans &t) const + std::string text (size_t index, const db::DFTrans &t) const { - return formatted (m_fmt, t); + return formatted (m_fmt, t, index); } /** @@ -695,7 +739,7 @@ protected: virtual void property_changed (); private: - db::DPoint m_p1, m_p2; + point_list m_points; int m_id; std::string m_fmt_x; std::string m_fmt_y; @@ -710,7 +754,7 @@ private: alignment_type m_xlabel_xalign, m_xlabel_yalign; alignment_type m_ylabel_xalign, m_ylabel_yalign; - std::string formatted (const std::string &fmt, const db::DFTrans &trans) const; + std::string formatted (const std::string &fmt, const db::DFTrans &trans, size_t index) const; }; } diff --git a/src/ant/ant/antPropertiesPage.cc b/src/ant/ant/antPropertiesPage.cc index b381c9c11..7b81af0f0 100644 --- a/src/ant/ant/antPropertiesPage.cc +++ b/src/ant/ant/antPropertiesPage.cc @@ -25,6 +25,7 @@ #include "antPropertiesPage.h" #include "layLayoutViewBase.h" #include "layQtTools.h" +#include "tlException.h" namespace ant { @@ -33,7 +34,7 @@ namespace ant // PropertiesPage implementation PropertiesPage::PropertiesPage (ant::Service *rulers, db::Manager *manager, QWidget *parent) - : lay::PropertiesPage (parent, manager, rulers), mp_rulers (rulers), m_enable_cb_callback (true) + : lay::PropertiesPage (parent, manager, rulers), mp_rulers (rulers), m_enable_cb_callback (true), m_in_text_changed (false) { mp_rulers->get_selection (m_selection); m_pos = m_selection.begin (); @@ -56,8 +57,10 @@ PropertiesPage::PropertiesPage (ant::Service *rulers, db::Manager *manager, QWid connect (fmt_le, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); connect (fmt_x_le, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); connect (fmt_y_le, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); + connect (x0, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); connect (x1, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); connect (x2, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); + connect (y0, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); connect (y1, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); connect (y2, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); @@ -71,6 +74,8 @@ PropertiesPage::PropertiesPage (ant::Service *rulers, db::Manager *manager, QWid connect (ylabel_xalign, SIGNAL (activated (int)), this, SIGNAL (edited ())); connect (ylabel_yalign, SIGNAL (activated (int)), this, SIGNAL (edited ())); + connect (points_edit, SIGNAL (textChanged ()), this, SLOT (text_changed ())); + } else { fmt_le->setReadOnly (true); @@ -172,6 +177,80 @@ PropertiesPage::get_points (db::DPoint &p1, db::DPoint &p2) p2 = db::DPoint (dx2, dy2); } +void +PropertiesPage::get_point (db::DPoint &p) +{ + double dx = 0.0, dy = 0.0; + bool has_error = false; + + try { + tl::from_string_ext (tl::to_string (x0->text ()), dx); + lay::indicate_error (x0, (tl::Exception *) 0); + } catch (tl::Exception &ex) { + lay::indicate_error (x0, &ex); + has_error = true; + } + + try { + tl::from_string_ext (tl::to_string (y0->text ()), dy); + lay::indicate_error (y0, (tl::Exception *) 0); + } catch (tl::Exception &ex) { + lay::indicate_error (y0, &ex); + has_error = true; + } + + if (has_error) { + throw tl::Exception (tl::to_string (tr ("At least one value is invalid - see highlighted entry fields"))); + } + + p = db::DPoint (dx, dy); +} + +void +PropertiesPage::get_points (ant::Object::point_list &points) +{ + std::string coordinates = tl::to_string (points_edit->toPlainText ()); + points.clear (); + + try { + + tl::Extractor ex (coordinates.c_str ()); + while (! ex.at_end ()) { + double x = 0.0, y = 0.0; + ex.read (x); + ex.test (","); + ex.read (y); + ex.test (";"); + ex.test (","); + points.push_back (db::DPoint (x, y)); + } + + lay::indicate_error (points_edit, (tl::Exception *) 0); + + } catch (tl::Exception &ex) { + lay::indicate_error (points_edit, &ex); + throw tl::Exception (tl::to_string (tr ("At least one value is invalid - see highlighted entry fields"))); + } +} + +void +PropertiesPage::text_changed () +{ + if (m_in_text_changed) { + return; + } + + try { + m_in_text_changed = true; + update_with (get_object ()); + emit edited (); + m_in_text_changed = false; + } catch (...) { + m_in_text_changed = false; + // ignore exceptions - the edit field will be highlighted anyway + } +} + void PropertiesPage::snap_to_layout_clicked () { @@ -296,32 +375,74 @@ void PropertiesPage::update () { mp_rulers->highlight (std::distance (m_selection.begin (), m_pos)); + update_with (current ()); +} - fmt_le->setText (tl::to_qstring (current ().fmt ())); - fmt_x_le->setText (tl::to_qstring (current ().fmt_x ())); - fmt_y_le->setText (tl::to_qstring (current ().fmt_y ())); - style_cb->setCurrentIndex (current ().style ()); - outline_cb->setCurrentIndex (current ().outline ()); +void +PropertiesPage::update_with (const ant::Object &obj) +{ + fmt_le->setText (tl::to_qstring (obj.fmt ())); + fmt_x_le->setText (tl::to_qstring (obj.fmt_x ())); + fmt_y_le->setText (tl::to_qstring (obj.fmt_y ())); + style_cb->setCurrentIndex (obj.style ()); + outline_cb->setCurrentIndex (obj.outline ()); - x1->setText (tl::to_qstring (tl::micron_to_string (current ().p1 ().x ()))); + main_position->setCurrentIndex (obj.main_position ()); + main_xalign->setCurrentIndex (obj.main_xalign ()); + main_yalign->setCurrentIndex (obj.main_yalign ()); + xlabel_xalign->setCurrentIndex (obj.xlabel_xalign ()); + xlabel_yalign->setCurrentIndex (obj.xlabel_yalign ()); + ylabel_xalign->setCurrentIndex (obj.ylabel_xalign ()); + ylabel_yalign->setCurrentIndex (obj.ylabel_yalign ()); + + int tab = 2; + if (obj.points ().size () == 1) { + tab = 0; + } else if (obj.points ().size () == 2) { + tab = 1; + } + + segments_tab->setTabEnabled (0, tab == 0); + segments_tab->setTabEnabled (1, tab == 1); + + if (! m_in_text_changed) { + segments_tab->setCurrentIndex (tab); + } + + point_list->clear (); + for (auto p = obj.points ().begin (); p != obj.points ().end (); ++p) { + QTreeWidgetItem *item = new QTreeWidgetItem (point_list); + item->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (tl::to_string (p->x ())))); + item->setData (1, Qt::DisplayRole, QVariant (tl::to_qstring (tl::to_string (p->y ())))); + } + + if (! m_in_text_changed) { + std::string text; + for (auto p = obj.points ().begin (); p != obj.points ().end (); ++p) { + text += tl::to_string (p->x ()); + text += ", "; + text += tl::to_string (p->y ()); + text += "\n"; + } + points_edit->setPlainText (tl::to_qstring (text)); + } + + x0->setText (tl::to_qstring (tl::micron_to_string (obj.p1 ().x ()))); + x0->setCursorPosition (0); + y0->setText (tl::to_qstring (tl::micron_to_string (obj.p1 ().y ()))); + y0->setCursorPosition (0); + + x1->setText (tl::to_qstring (tl::micron_to_string (obj.p1 ().x ()))); x1->setCursorPosition (0); - x2->setText (tl::to_qstring (tl::micron_to_string (current ().p2 ().x ()))); + x2->setText (tl::to_qstring (tl::micron_to_string (obj.p2 ().x ()))); x2->setCursorPosition (0); - y1->setText (tl::to_qstring (tl::micron_to_string (current ().p1 ().y ()))); + y1->setText (tl::to_qstring (tl::micron_to_string (obj.p1 ().y ()))); y1->setCursorPosition (0); - y2->setText (tl::to_qstring (tl::micron_to_string (current ().p2 ().y ()))); + y2->setText (tl::to_qstring (tl::micron_to_string (obj.p2 ().y ()))); y2->setCursorPosition (0); - main_position->setCurrentIndex (current ().main_position ()); - main_xalign->setCurrentIndex (current ().main_xalign ()); - main_yalign->setCurrentIndex (current ().main_yalign ()); - xlabel_xalign->setCurrentIndex (current ().xlabel_xalign ()); - xlabel_yalign->setCurrentIndex (current ().xlabel_yalign ()); - ylabel_xalign->setCurrentIndex (current ().ylabel_xalign ()); - ylabel_yalign->setCurrentIndex (current ().ylabel_yalign ()); - - double sx = (current ().p2 ().x () - current ().p1 ().x ()); - double sy = (current ().p2 ().y () - current ().p1 ().y ()); + double sx = (obj.p2 ().x () - obj.p1 ().x ()); + double sy = (obj.p2 ().y () - obj.p1 ().y ()); dx->setText (tl::to_qstring (tl::micron_to_string (sx))); dx->setCursorPosition (0); dy->setText (tl::to_qstring (tl::micron_to_string (sy))); @@ -339,9 +460,13 @@ PropertiesPage::readonly () void PropertiesPage::apply () { - // only adjust the values if the text has changed - db::DPoint p1, p2; - get_points (p1, p2); + mp_rulers->change_ruler (*m_pos, get_object ()); +} + +ant::Object +PropertiesPage::get_object () +{ + ant::Object ruler; std::string fmt = tl::to_string (fmt_le->text ()); std::string fmt_x = tl::to_string (fmt_x_le->text ()); @@ -349,7 +474,26 @@ PropertiesPage::apply () Object::style_type style = Object::style_type (style_cb->currentIndex ()); Object::outline_type outline = Object::outline_type (outline_cb->currentIndex ()); - ant::Object ruler (p1, p2, current ().id (), fmt_x, fmt_y, fmt, style, outline, current ().snap (), current ().angle_constraint ()); + if (segments_tab->currentIndex () == 0 || segments_tab->currentIndex () == 1) { + + db::DPoint p1, p2; + if (segments_tab->currentIndex () == 0) { + get_points (p1, p2); + } else { + get_point (p1); + p2 = p1; + } + + ruler = ant::Object (p1, p2, current ().id (), fmt_x, fmt_y, fmt, style, outline, current ().snap (), current ().angle_constraint ()); + + } else if (segments_tab->currentIndex () == 2 || segments_tab->currentIndex () == 3) { + + ant::Object::point_list points; + get_points (points); + + ruler = ant::Object (points, current ().id (), fmt_x, fmt_y, fmt, style, outline, current ().snap (), current ().angle_constraint ()); + + } ruler.set_main_position (Object::position_type (main_position->currentIndex ())); ruler.set_main_xalign (Object::alignment_type (main_xalign->currentIndex ())); @@ -361,7 +505,7 @@ PropertiesPage::apply () ruler.set_category (current ().category ()); - mp_rulers->change_ruler (*m_pos, ruler); + return ruler; } } diff --git a/src/ant/ant/antPropertiesPage.h b/src/ant/ant/antPropertiesPage.h index 8e50f4dc4..26e351e2d 100644 --- a/src/ant/ant/antPropertiesPage.h +++ b/src/ant/ant/antPropertiesPage.h @@ -52,20 +52,26 @@ public: virtual void update (); virtual void leave (); virtual bool readonly (); - virtual void apply (); + virtual void apply (); private slots: void swap_points_clicked (); void snap_to_layout_clicked (); + void text_changed (); private: std::vector m_selection; std::vector ::iterator m_pos; ant::Service *mp_rulers; bool m_enable_cb_callback; + bool m_in_text_changed; const ant::Object ¤t () const; - void get_points(db::DPoint &p1, db::DPoint &p2); + void get_points (db::DPoint &p1, db::DPoint &p2); + void get_point (db::DPoint &p); + void get_points (ant::Object::point_list &points); + void update_with (const ant::Object &obj); + ant::Object get_object (); }; } diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index b7a4f55a9..a93c05885 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -570,21 +570,23 @@ draw_ellipse (const db::DPoint &q1, } void -draw_ruler (const ant::Object &ruler, const db::DCplxTrans &trans, bool sel, lay::CanvasPlane *bitmap, lay::Renderer &renderer) +draw_ruler_segment (const ant::Object &ruler, size_t index, const db::DCplxTrans &trans, bool sel, lay::CanvasPlane *bitmap, lay::Renderer &renderer) { + db::DPoint p1 = ruler.seg_p1 (index), p2 = ruler.seg_p2 (index); + // round the starting point, shift both, and round the end point - std::pair v = lay::snap (trans * ruler.p1 (), trans * ruler.p2 ()); + std::pair v = lay::snap (trans * p1, trans * p2); db::DPoint q1 = v.first; db::DPoint q2 = v.second; bool xy_swapped = ((trans.rot () % 2) != 0); - double lu = ruler.p1 ().double_distance (ruler.p2 ()); + double lu = p1.double_distance (p2); int min_tick_spc = int (0.5 + 20 / renderer.resolution ()); // min tick spacing in canvas units double mu = double (min_tick_spc) / trans.ctrans (1.0); if (ruler.outline () == Object::OL_diag) { draw_ruler (q1, q2, lu, mu, sel, q2.x () < q1.x (), ruler.style (), bitmap, renderer); - draw_text (q1, q2, lu, ruler.text (), q2.x () < q1.x (), ruler.style (), ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); + draw_text (q1, q2, lu, ruler.text (index), q2.x () < q1.x (), ruler.style (), ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); } if ((!xy_swapped && (ruler.outline () == Object::OL_xy || ruler.outline () == Object::OL_diag_xy)) || @@ -594,12 +596,12 @@ draw_ruler (const ant::Object &ruler, const db::DCplxTrans &trans, bool sel, lay if (ruler.outline () == Object::OL_diag_xy || ruler.outline () == Object::OL_diag_yx) { draw_ruler (q1, q2, lu, mu, sel, !r, ruler.style (), bitmap, renderer); - draw_text (q1, q2, lu, ruler.text (), !r, ruler.style (), ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); + draw_text (q1, q2, lu, ruler.text (index), !r, ruler.style (), ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); } draw_ruler (q1, db::DPoint (q2.x (), q1.y ()), lu, mu, sel, r, ruler.style (), bitmap, renderer); - draw_text (q1, db::DPoint (q2.x (), q1.y ()), lu, ruler.text_x (trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); + draw_text (q1, db::DPoint (q2.x (), q1.y ()), lu, ruler.text_x (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); draw_ruler (db::DPoint (q2.x (), q1.y ()), q2, lu, mu, sel, r, ruler.style (), bitmap, renderer); - draw_text (db::DPoint (q2.x (), q1.y ()), q2, lu, ruler.text_y (trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer); + draw_text (db::DPoint (q2.x (), q1.y ()), q2, lu, ruler.text_y (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer); } @@ -610,12 +612,12 @@ draw_ruler (const ant::Object &ruler, const db::DCplxTrans &trans, bool sel, lay if (ruler.outline () == Object::OL_diag_xy || ruler.outline () == Object::OL_diag_yx) { draw_ruler (q1, q2, lu, mu, sel, !r, ruler.style (), bitmap, renderer); - draw_text (q1, q2, lu, ruler.text (), !r, ruler.style (), ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); + draw_text (q1, q2, lu, ruler.text (index), !r, ruler.style (), ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); } draw_ruler (q1, db::DPoint (q1.x (), q2.y ()), lu, mu, sel, r, ruler.style (), bitmap, renderer); - draw_text (q1, db::DPoint (q1.x (), q2.y ()), lu, ruler.text_y (trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer); + draw_text (q1, db::DPoint (q1.x (), q2.y ()), lu, ruler.text_y (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer); draw_ruler (db::DPoint (q1.x (), q2.y ()), q2, lu, mu, sel, r, ruler.style (), bitmap, renderer); - draw_text (db::DPoint (q1.x (), q2.y ()), q2, lu, ruler.text_x (trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); + draw_text (db::DPoint (q1.x (), q2.y ()), q2, lu, ruler.text_x (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); } @@ -624,34 +626,43 @@ draw_ruler (const ant::Object &ruler, const db::DCplxTrans &trans, bool sel, lay bool r = (q2.x () > q1.x ()) ^ (q2.y () < q1.y ()); draw_ruler (q1, db::DPoint (q2.x (), q1.y ()), lu, mu, sel, r, ruler.style (), bitmap, renderer); - draw_text (q1, db::DPoint (q2.x (), q1.y ()), lu, ruler.text_x (trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); + draw_text (q1, db::DPoint (q2.x (), q1.y ()), lu, ruler.text_x (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); draw_ruler (db::DPoint (q2.x (), q1.y ()), q2, lu, mu, sel, r, ruler.style (), bitmap, renderer); - draw_text (db::DPoint (q2.x (), q1.y ()), q2, lu, ruler.text_y (trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer); + draw_text (db::DPoint (q2.x (), q1.y ()), q2, lu, ruler.text_y (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer); draw_ruler (q1, db::DPoint (q1.x (), q2.y ()), lu, mu, sel, !r, ruler.style (), bitmap, renderer); draw_ruler (db::DPoint (q1.x (), q2.y ()), q2, lu, mu, sel, !r, ruler.style (), bitmap, renderer); - draw_text (q1, q2, lu, ruler.text (), !r, ant::Object::STY_none, ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); + draw_text (q1, q2, lu, ruler.text (index), !r, ant::Object::STY_none, ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); } else if (ruler.outline () == Object::OL_ellipse) { bool r = (q2.x () > q1.x ()) ^ (q2.y () < q1.y ()); - draw_text (q1, db::DPoint (q2.x (), q1.y ()), lu, ruler.text_x (trans.fp_trans ()), r, ant::Object::STY_none, ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); - draw_text (db::DPoint (q2.x (), q1.y ()), q2, lu, ruler.text_y (trans.fp_trans ()), r, ant::Object::STY_none, ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer); - draw_text (q1, q2, lu, ruler.text (), !r, ant::Object::STY_none, ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); + draw_text (q1, db::DPoint (q2.x (), q1.y ()), lu, ruler.text_x (index, trans.fp_trans ()), r, ant::Object::STY_none, ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); + draw_text (db::DPoint (q2.x (), q1.y ()), q2, lu, ruler.text_y (index, trans.fp_trans ()), r, ant::Object::STY_none, ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer); + draw_text (q1, q2, lu, ruler.text (index), !r, ant::Object::STY_none, ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); draw_ellipse (q1, q2, lu, sel, bitmap, renderer); } } -static bool -is_selected (const ant::Object &ruler, const db::DPoint &pos, double enl, double &distance) +void +draw_ruler (const ant::Object &ruler, const db::DCplxTrans &trans, bool sel, lay::CanvasPlane *bitmap, lay::Renderer &renderer) { + for (size_t index = 0; index < ruler.segments (); ++index) { + draw_ruler_segment (ruler, index, trans, sel, bitmap, renderer); + } +} + +static bool +is_selected (const ant::Object &ruler, size_t index, const db::DPoint &pos, double enl, double &distance) +{ + db::DPoint p1 = ruler.seg_p1 (index), p2 = ruler.seg_p2 (index); + db::DBox b (p1, p2); + if (ruler.outline () == ant::Object::OL_ellipse) { // special handling of the (non-degenerated) ellipse case - db::DBox b (ruler.p1 (), ruler.p2 ()); - if (b.height () > 1e-6 && b.width () > 1e-6) { double dx = (pos.x () - b.center ().x ()) / (b.width () * 0.5); @@ -674,8 +685,6 @@ is_selected (const ant::Object &ruler, const db::DPoint &pos, double enl, double } - db::DBox b (ruler.p1 (), ruler.p2 ()); - // enlarge this box by some pixels b.enlarge (db::DVector (enl, enl)); @@ -717,10 +726,23 @@ is_selected (const ant::Object &ruler, const db::DPoint &pos, double enl, double return false; } +static bool +is_selected (const ant::Object &ruler, const db::DPoint &pos, double enl, double &distance) +{ + bool any = false; + for (size_t index = 0; index < ruler.segments (); ++index) { + // NOTE: we check *all* since distance is updated herein. + if (is_selected (ruler, index, pos, enl, distance)) { + any = true; + } + } + return any; +} + static bool is_selected (const ant::Object &ruler, const db::DBox &box, double /*enl*/) { - return (box.contains (ruler.p1 ()) && box.contains (ruler.p2 ())); + return ruler.box ().inside (box); } @@ -2180,7 +2202,11 @@ Service::display_status (bool transient) if (! transient) { msg = tl::to_string (tr ("selected: ")); } - msg += tl::sprintf (tl::to_string (tr ("annotation(d=%s x=%s y=%s)")), ruler->text (), ruler->text_x (), ruler->text_y ()); + if (ruler->segments () > 1) { + msg += tl::sprintf (tl::to_string (tr ("annotation(d=%s x=%s y=%s ...)")), ruler->text (0), ruler->text_x (0), ruler->text_y (0)); + } else { + msg += tl::sprintf (tl::to_string (tr ("annotation(d=%s x=%s y=%s)")), ruler->text (0), ruler->text_x (0), ruler->text_y (0)); + } view ()->message (msg); } diff --git a/src/ant/ant/gsiDeclAnt.cc b/src/ant/ant/gsiDeclAnt.cc index a1284d3ff..949cb5c09 100644 --- a/src/ant/ant/gsiDeclAnt.cc +++ b/src/ant/ant/gsiDeclAnt.cc @@ -236,6 +236,13 @@ static AnnotationRef create_measure_ruler (lay::LayoutViewBase *view, const db:: } } +static AnnotationRef *ant_from_s (const std::string &s) +{ + std::unique_ptr aref (new AnnotationRef ()); + aref->from_string (s.c_str ()); + return aref.release (); +} + static int get_style (const AnnotationRef *obj) { return int (obj->style ()); @@ -704,27 +711,80 @@ gsi::Class decl_Annotation (decl_BasicAnnotation, "lay", "Annotat "\n" "This method has been introduced in version 0.25." ) + - gsi::method ("p1", (const db::DPoint & (AnnotationRef::*) () const) &AnnotationRef::p1, + gsi::method ("points", &AnnotationRef::points, + "@brief Gets the points of the ruler\n" + "A single-segmented ruler has two points. Rulers with more points " + "have more segments correspondingly. Note that the point list may have one point " + "only (single-point ruler) or may even be empty.\n" + "\n" + "Use \\points= to set the segment points. Use \\segments to get the number of " + "segments and \\seg_p1 and \\seg_p2 to get the first and second point of one segment.\n" + "\n" + "Multi-segmented rulers have been introduced in version 0.28" + ) + + gsi::method ("points=", &AnnotationRef::set_points, gsi::arg ("points"), + "@brief Sets the points for a (potentially) multi-segmented ruler\n" + "See \\points for a description of multi-segmented rulers. " + "The list of points passed to this method is cleaned from duplicates before being " + "stored inside the ruler.\n" + "\n" + "This method has been introduced in version 0.28." + ) + + gsi::method ("segments", &AnnotationRef::segments, + "@brief Gets the number of segments.\n" + "This method returns the number of segments the ruler is made up. Even though the " + "ruler can be one or even zero points, the number of segments is at least 1.\n" + "\n" + "This method has been introduced in version 0.28." + ) + + gsi::method ("seg_p1", &AnnotationRef::seg_p1, gsi::arg ("segment_index"), + "@brief Gets the first point of the given segment.\n" + "The segment is indicated by the segment index which is a number between 0 and \\segments-1.\n" + "\n" + "This method has been introduced in version 0.28." + ) + + gsi::method ("seg_p2", &AnnotationRef::seg_p2, gsi::arg ("segment_index"), + "@brief Gets the second point of the given segment.\n" + "The segment is indicated by the segment index which is a number between 0 and \\segments-1.\n" + "The second point of a segment is also the first point of the following segment if there is one.\n" + "\n" + "This method has been introduced in version 0.28." + ) + + gsi::method ("p1", (db::DPoint (AnnotationRef::*) () const) &AnnotationRef::p1, "@brief Gets the first point of the ruler or marker\n" "The points of the ruler or marker are always given in micron units in floating-point " "coordinates.\n" + "\n" + "This method is provided for backward compatibility. Starting with version 0.28, rulers can " + "be multi-segmented. Use \\points or \\seg_p1 to retrieve the points of the ruler segments.\n" + "\n" "@return The first point\n" ) + - gsi::method ("p2", (const db::DPoint & (AnnotationRef::*) () const) &AnnotationRef::p2, + gsi::method ("p2", (db::DPoint (AnnotationRef::*) () const) &AnnotationRef::p2, "@brief Gets the second point of the ruler or marker\n" "The points of the ruler or marker are always given in micron units in floating-point " "coordinates.\n" + "\n" + "This method is provided for backward compatibility. Starting with version 0.28, rulers can " + "be multi-segmented. Use \\points or \\seg_p1 to retrieve the points of the ruler segments.\n" + "\n" "@return The second point\n" ) + gsi::method ("p1=", (void (AnnotationRef::*) (const db::DPoint &)) &AnnotationRef::p1, gsi::arg ("point"), "@brief Sets the first point of the ruler or marker\n" "The points of the ruler or marker are always given in micron units in floating-point " "coordinates.\n" + "\n" + "This method is provided for backward compatibility. Starting with version 0.28, rulers can " + "be multi-segmented. Use \\points= to specify the ruler segments.\n" ) + gsi::method ("p2=", (void (AnnotationRef::*) (const db::DPoint &)) &AnnotationRef::p2, gsi::arg ("point"), "@brief Sets the second point of the ruler or marker\n" "The points of the ruler or marker are always given in micron units in floating-point " "coordinates.\n" + "\n" + "This method is provided for backward compatibility. Starting with version 0.28, rulers can " + "be multi-segmented. Use \\points= to specify the ruler segments.\n" ) + gsi::method ("box", &AnnotationRef::box, "@brief Gets the bounding box of the object (not including text)\n" @@ -924,14 +984,17 @@ gsi::Class decl_Annotation (decl_BasicAnnotation, "lay", "Annotat "@brief Returns the angle constraint attribute\n" "See \\angle_constraint= for a more detailed description." ) + - gsi::method ("text_x", (std::string (AnnotationRef::*)() const) &AnnotationRef::text_x, - "@brief Returns the formatted text for the x-axis label" + gsi::method ("text_x", (std::string (AnnotationRef::*)(size_t index) const) &AnnotationRef::text_x, gsi::arg ("index", 0), + "@brief Returns the formatted text for the x-axis label\n" + "The index parameter indicates which segment to use (0 is the first one). It has been added in version 0.28.\n" ) + - gsi::method ("text_y", (std::string (AnnotationRef::*)() const) &AnnotationRef::text_y, - "@brief Returns the formatted text for the y-axis label" + gsi::method ("text_y", (std::string (AnnotationRef::*)(size_t index) const) &AnnotationRef::text_y, gsi::arg ("index", 0), + "@brief Returns the formatted text for the y-axis label\n" + "The index parameter indicates which segment to use (0 is the first one). It has been added in version 0.28.\n" ) + - gsi::method ("text", (std::string (AnnotationRef::*)() const) &AnnotationRef::text, - "@brief Returns the formatted text for the main label" + gsi::method ("text", (std::string (AnnotationRef::*)(size_t index) const) &AnnotationRef::text, gsi::arg ("index", 0), + "@brief Returns the formatted text for the main label\n" + "The index parameter indicates which segment to use (0 is the first one). It has been added in version 0.28.\n" ) + gsi::method ("id", (int (AnnotationRef::*)() const) &AnnotationRef::id, "@brief Returns the annotation's ID" @@ -953,6 +1016,12 @@ gsi::Class decl_Annotation (decl_BasicAnnotation, "lay", "Annotat "\n" "This method was introduced in version 0.19." ) + + gsi::constructor ("from_s", &ant_from_s, gsi::arg ("s"), + "@brief Creates a ruler from a string representation\n" + "This function creates a ruler from the string returned by \\to_s.\n" + "\n" + "This method was introduced in version 0.28." + ) + gsi::method ("==", &AnnotationRef::operator==, gsi::arg ("other"), "@brief Equality operator\n" ) + From 1c8a99fa269a8906d24764a345606422f3604f36 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 26 Sep 2022 23:52:32 +0200 Subject: [PATCH 10/54] WIP: some bug fixing --- src/ant/ant/antPropertiesPage.cc | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ant/ant/antPropertiesPage.cc b/src/ant/ant/antPropertiesPage.cc index 7b81af0f0..035755297 100644 --- a/src/ant/ant/antPropertiesPage.cc +++ b/src/ant/ant/antPropertiesPage.cc @@ -402,9 +402,6 @@ PropertiesPage::update_with (const ant::Object &obj) tab = 1; } - segments_tab->setTabEnabled (0, tab == 0); - segments_tab->setTabEnabled (1, tab == 1); - if (! m_in_text_changed) { segments_tab->setCurrentIndex (tab); } @@ -412,19 +409,23 @@ PropertiesPage::update_with (const ant::Object &obj) point_list->clear (); for (auto p = obj.points ().begin (); p != obj.points ().end (); ++p) { QTreeWidgetItem *item = new QTreeWidgetItem (point_list); - item->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (tl::to_string (p->x ())))); - item->setData (1, Qt::DisplayRole, QVariant (tl::to_qstring (tl::to_string (p->y ())))); + item->setData (0, Qt::DisplayRole, QVariant (tl::to_qstring (tl::micron_to_string (p->x ())))); + item->setData (1, Qt::DisplayRole, QVariant (tl::to_qstring (tl::micron_to_string (p->y ())))); } if (! m_in_text_changed) { + std::string text; for (auto p = obj.points ().begin (); p != obj.points ().end (); ++p) { - text += tl::to_string (p->x ()); + text += tl::micron_to_string (p->x ()); text += ", "; - text += tl::to_string (p->y ()); + text += tl::micron_to_string (p->y ()); text += "\n"; } + + QSignalBlocker blocker (points_edit); points_edit->setPlainText (tl::to_qstring (text)); + } x0->setText (tl::to_qstring (tl::micron_to_string (obj.p1 ().x ()))); @@ -477,7 +478,7 @@ PropertiesPage::get_object () if (segments_tab->currentIndex () == 0 || segments_tab->currentIndex () == 1) { db::DPoint p1, p2; - if (segments_tab->currentIndex () == 0) { + if (segments_tab->currentIndex () == 1) { get_points (p1, p2); } else { get_point (p1); From 79102d399e468d08fa3c033aaf71f949be228b86 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 27 Sep 2022 22:56:34 +0200 Subject: [PATCH 11/54] WIP: enhancements, debugging of multi-segment rulers setup --- src/ant/ant/antObject.cc | 8 +- src/ant/ant/antObject.h | 2 +- src/ant/ant/antPropertiesPage.cc | 146 +++++++++++++++++++------------ src/ant/ant/antPropertiesPage.h | 6 +- src/ant/ant/antService.cc | 104 ++++++++++++++-------- 5 files changed, 171 insertions(+), 95 deletions(-) diff --git a/src/ant/ant/antObject.cc b/src/ant/ant/antObject.cc index 5603de0d8..1dedd9281 100644 --- a/src/ant/ant/antObject.cc +++ b/src/ant/ant/antObject.cc @@ -247,7 +247,9 @@ Object::set_points (const point_list &points) db::DPoint Object::seg_p1 (size_t seg_index) const { - if (seg_index < m_points.size ()) { + if (seg_index == std::numeric_limits::max ()) { + return p1 (); + } else if (seg_index < m_points.size ()) { return m_points[seg_index]; } else if (m_points.empty ()) { return db::DPoint (); @@ -259,7 +261,9 @@ Object::seg_p1 (size_t seg_index) const db::DPoint Object::seg_p2 (size_t seg_index) const { - if (seg_index + 1 < m_points.size ()) { + if (seg_index == std::numeric_limits::max ()) { + return p2 (); + } else if (seg_index + 1 < m_points.size ()) { return m_points[seg_index + 1]; } else if (m_points.empty ()) { return db::DPoint (); diff --git a/src/ant/ant/antObject.h b/src/ant/ant/antObject.h index 6358417c0..7304c5bed 100644 --- a/src/ant/ant/antObject.h +++ b/src/ant/ant/antObject.h @@ -329,7 +329,7 @@ public: */ db::DPoint p2 () const { - return seg_p2 (0); + return seg_p2 (segments () - 1); } /** diff --git a/src/ant/ant/antPropertiesPage.cc b/src/ant/ant/antPropertiesPage.cc index 035755297..7e55c2afc 100644 --- a/src/ant/ant/antPropertiesPage.cc +++ b/src/ant/ant/antPropertiesPage.cc @@ -30,11 +30,39 @@ namespace ant { +// ------------------------------------------------------------------------- +// A ruler that tells us if he was modified + +class RulerWithModifiedProperty + : public ant::Object +{ +public: + RulerWithModifiedProperty () + : ant::Object (), m_modified (false) + { + // .. nothing yet .. + } + + bool is_modified () const + { + return m_modified; + } + +protected: + void property_changed () + { + m_modified = true; + ant::Object::property_changed (); + } + + bool m_modified; +}; + // ------------------------------------------------------------------------- // PropertiesPage implementation PropertiesPage::PropertiesPage (ant::Service *rulers, db::Manager *manager, QWidget *parent) - : lay::PropertiesPage (parent, manager, rulers), mp_rulers (rulers), m_enable_cb_callback (true), m_in_text_changed (false) + : lay::PropertiesPage (parent, manager, rulers), mp_rulers (rulers), m_enable_cb_callback (true), m_in_something_changed (false) { mp_rulers->get_selection (m_selection); m_pos = m_selection.begin (); @@ -54,27 +82,27 @@ PropertiesPage::PropertiesPage (ant::Service *rulers, db::Manager *manager, QWid if (! readonly ()) { - connect (fmt_le, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); - connect (fmt_x_le, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); - connect (fmt_y_le, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); - connect (x0, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); - connect (x1, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); - connect (x2, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); - connect (y0, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); - connect (y1, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); - connect (y2, SIGNAL (editingFinished ()), this, SIGNAL (edited ())); + connect (fmt_le, SIGNAL (editingFinished ()), this, SLOT (something_changed ())); + connect (fmt_x_le, SIGNAL (editingFinished ()), this, SLOT (something_changed ())); + connect (fmt_y_le, SIGNAL (editingFinished ()), this, SLOT (something_changed ())); + connect (x0, SIGNAL (editingFinished ()), this, SLOT (something_changed ())); + connect (x1, SIGNAL (editingFinished ()), this, SLOT (something_changed ())); + connect (x2, SIGNAL (editingFinished ()), this, SLOT (something_changed ())); + connect (y0, SIGNAL (editingFinished ()), this, SLOT (something_changed ())); + connect (y1, SIGNAL (editingFinished ()), this, SLOT (something_changed ())); + connect (y2, SIGNAL (editingFinished ()), this, SLOT (something_changed ())); - connect (style_cb, SIGNAL (activated (int)), this, SIGNAL (edited ())); - connect (outline_cb, SIGNAL (activated (int)), this, SIGNAL (edited ())); - connect (main_position, SIGNAL (activated (int)), this, SIGNAL (edited ())); - connect (main_xalign, SIGNAL (activated (int)), this, SIGNAL (edited ())); - connect (main_yalign, SIGNAL (activated (int)), this, SIGNAL (edited ())); - connect (xlabel_xalign, SIGNAL (activated (int)), this, SIGNAL (edited ())); - connect (xlabel_yalign, SIGNAL (activated (int)), this, SIGNAL (edited ())); - connect (ylabel_xalign, SIGNAL (activated (int)), this, SIGNAL (edited ())); - connect (ylabel_yalign, SIGNAL (activated (int)), this, SIGNAL (edited ())); + connect (style_cb, SIGNAL (activated (int)), this, SLOT (something_changed ())); + connect (outline_cb, SIGNAL (activated (int)), this, SLOT (something_changed ())); + connect (main_position, SIGNAL (activated (int)), this, SLOT (something_changed ())); + connect (main_xalign, SIGNAL (activated (int)), this, SLOT (something_changed ())); + connect (main_yalign, SIGNAL (activated (int)), this, SLOT (something_changed ())); + connect (xlabel_xalign, SIGNAL (activated (int)), this, SLOT (something_changed ())); + connect (xlabel_yalign, SIGNAL (activated (int)), this, SLOT (something_changed ())); + connect (ylabel_xalign, SIGNAL (activated (int)), this, SLOT (something_changed ())); + connect (ylabel_yalign, SIGNAL (activated (int)), this, SLOT (something_changed ())); - connect (points_edit, SIGNAL (textChanged ()), this, SLOT (text_changed ())); + connect (points_edit, SIGNAL (textChanged ()), this, SLOT (something_changed ())); } else { @@ -234,19 +262,28 @@ PropertiesPage::get_points (ant::Object::point_list &points) } void -PropertiesPage::text_changed () +PropertiesPage::something_changed () { - if (m_in_text_changed) { + if (m_in_something_changed) { return; } try { - m_in_text_changed = true; - update_with (get_object ()); - emit edited (); - m_in_text_changed = false; + + m_in_something_changed = true; + + RulerWithModifiedProperty obj; + obj.ant::Object::operator= (current ()); + get_object (obj); + if (obj.is_modified ()) { + update_with (obj); + emit edited (); + } + + m_in_something_changed = false; + } catch (...) { - m_in_text_changed = false; + m_in_something_changed = false; // ignore exceptions - the edit field will be highlighted anyway } } @@ -395,16 +432,20 @@ PropertiesPage::update_with (const ant::Object &obj) ylabel_xalign->setCurrentIndex (obj.ylabel_xalign ()); ylabel_yalign->setCurrentIndex (obj.ylabel_yalign ()); - int tab = 2; - if (obj.points ().size () == 1) { - tab = 0; - } else if (obj.points ().size () == 2) { - tab = 1; - } - - if (! m_in_text_changed) { - segments_tab->setCurrentIndex (tab); + // change tabs if required + if (segments_tab->currentIndex () == 1) { + if (obj.points ().size () > 2 || obj.points ().size () == 0) { + segments_tab->setCurrentIndex (2); + } + } else if (segments_tab->currentIndex () == 0) { + if (obj.points ().size () > 2 || obj.points ().size () == 0) { + segments_tab->setCurrentIndex (2); + } else if (obj.points ().size () > 1) { + segments_tab->setCurrentIndex (1); + } } + segments_tab->setTabEnabled (0, obj.points ().size () == 1); + segments_tab->setTabEnabled (1, obj.points ().size () <= 2 && obj.points ().size () > 0); point_list->clear (); for (auto p = obj.points ().begin (); p != obj.points ().end (); ++p) { @@ -413,7 +454,7 @@ PropertiesPage::update_with (const ant::Object &obj) item->setData (1, Qt::DisplayRole, QVariant (tl::to_qstring (tl::micron_to_string (p->y ())))); } - if (! m_in_text_changed) { + if (! m_in_something_changed || segments_tab->currentIndex () != 3) { std::string text; for (auto p = obj.points ().begin (); p != obj.points ().end (); ++p) { @@ -461,14 +502,13 @@ PropertiesPage::readonly () void PropertiesPage::apply () { - mp_rulers->change_ruler (*m_pos, get_object ()); + ant::Object obj; + get_object (obj); + mp_rulers->change_ruler (*m_pos, obj); } -ant::Object -PropertiesPage::get_object () +void PropertiesPage::get_object(ant::Object &obj) { - ant::Object ruler; - std::string fmt = tl::to_string (fmt_le->text ()); std::string fmt_x = tl::to_string (fmt_x_le->text ()); std::string fmt_y = tl::to_string (fmt_y_le->text ()); @@ -485,28 +525,26 @@ PropertiesPage::get_object () p2 = p1; } - ruler = ant::Object (p1, p2, current ().id (), fmt_x, fmt_y, fmt, style, outline, current ().snap (), current ().angle_constraint ()); + obj = ant::Object (p1, p2, current ().id (), fmt_x, fmt_y, fmt, style, outline, current ().snap (), current ().angle_constraint ()); } else if (segments_tab->currentIndex () == 2 || segments_tab->currentIndex () == 3) { ant::Object::point_list points; get_points (points); - ruler = ant::Object (points, current ().id (), fmt_x, fmt_y, fmt, style, outline, current ().snap (), current ().angle_constraint ()); + obj = ant::Object (points, current ().id (), fmt_x, fmt_y, fmt, style, outline, current ().snap (), current ().angle_constraint ()); } - ruler.set_main_position (Object::position_type (main_position->currentIndex ())); - ruler.set_main_xalign (Object::alignment_type (main_xalign->currentIndex ())); - ruler.set_main_yalign (Object::alignment_type (main_yalign->currentIndex ())); - ruler.set_xlabel_xalign (Object::alignment_type (xlabel_xalign->currentIndex ())); - ruler.set_xlabel_yalign (Object::alignment_type (xlabel_yalign->currentIndex ())); - ruler.set_ylabel_xalign (Object::alignment_type (ylabel_xalign->currentIndex ())); - ruler.set_ylabel_yalign (Object::alignment_type (ylabel_yalign->currentIndex ())); + obj.set_main_position (Object::position_type (main_position->currentIndex ())); + obj.set_main_xalign (Object::alignment_type (main_xalign->currentIndex ())); + obj.set_main_yalign (Object::alignment_type (main_yalign->currentIndex ())); + obj.set_xlabel_xalign (Object::alignment_type (xlabel_xalign->currentIndex ())); + obj.set_xlabel_yalign (Object::alignment_type (xlabel_yalign->currentIndex ())); + obj.set_ylabel_xalign (Object::alignment_type (ylabel_xalign->currentIndex ())); + obj.set_ylabel_yalign (Object::alignment_type (ylabel_yalign->currentIndex ())); - ruler.set_category (current ().category ()); - - return ruler; + obj.set_category (current ().category ()); } } diff --git a/src/ant/ant/antPropertiesPage.h b/src/ant/ant/antPropertiesPage.h index 26e351e2d..1f1ead4da 100644 --- a/src/ant/ant/antPropertiesPage.h +++ b/src/ant/ant/antPropertiesPage.h @@ -57,21 +57,21 @@ public: private slots: void swap_points_clicked (); void snap_to_layout_clicked (); - void text_changed (); + void something_changed (); private: std::vector m_selection; std::vector ::iterator m_pos; ant::Service *mp_rulers; bool m_enable_cb_callback; - bool m_in_text_changed; + bool m_in_something_changed; const ant::Object ¤t () const; void get_points (db::DPoint &p1, db::DPoint &p2); void get_point (db::DPoint &p); void get_points (ant::Object::point_list &points); void update_with (const ant::Object &obj); - ant::Object get_object (); + void get_object (ant::Object &obj); }; } diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index a93c05885..ffd1e22cb 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -113,6 +113,8 @@ tick_spacings (double d, double min_d, int &minor_ticks, double &ticks) * @param pos The position where to draw the text * @param bitmap The bitmap to draw the ruler on * @param renderer The renderer object + * @param first_segment True, if we're drawing the first segment + * @param last_segment True, if we're drawing the last segment */ void draw_ruler (const db::DPoint &q1, @@ -123,7 +125,9 @@ draw_ruler (const db::DPoint &q1, bool right, ant::Object::style_type style, lay::CanvasPlane *bitmap, - lay::Renderer &renderer) + lay::Renderer &renderer, + bool first_segment, + bool last_segment) { double arrow_width = 8 / renderer.resolution (); double arrow_length = 12 / renderer.resolution (); @@ -189,12 +193,16 @@ draw_ruler (const db::DPoint &q1, db::DVector qw = qq * (sel_width * 0.5); db::DVector dq1, dq2; - if (style == ant::Object::STY_arrow_both || style == ant::Object::STY_arrow_start) { + if (! first_segment) { + // no start indicator if not first segment + } else if (style == ant::Object::STY_arrow_both || style == ant::Object::STY_arrow_start) { dq1 = qu * (arrow_length - 1); } else if (style == ant::Object::STY_cross_both || style == ant::Object::STY_cross_start) { dq1 = qu * (sel_width * 0.5); } - if (style == ant::Object::STY_arrow_both || style == ant::Object::STY_arrow_end) { + if (! last_segment) { + // no end indicator if not last segment + } else if (style == ant::Object::STY_arrow_both || style == ant::Object::STY_arrow_end) { dq2 = qu * -(arrow_length - 1); } else if (style == ant::Object::STY_cross_both || style == ant::Object::STY_cross_end) { dq2 = qu * -(sel_width * 0.5); @@ -212,7 +220,11 @@ draw_ruler (const db::DPoint &q1, } - if (style == ant::Object::STY_arrow_end || style == ant::Object::STY_arrow_both) { + if (! last_segment) { + + // no end indicator if not last segment + + } else if (style == ant::Object::STY_arrow_end || style == ant::Object::STY_arrow_both) { db::DPolygon p; db::DPoint points[] = { @@ -239,7 +251,11 @@ draw_ruler (const db::DPoint &q1, } - if (style == ant::Object::STY_arrow_start || style == ant::Object::STY_arrow_both) { + if (! first_segment) { + + // no start indicator if not first segment + + } else if (style == ant::Object::STY_arrow_start || style == ant::Object::STY_arrow_both) { db::DPolygon p; db::DPoint points[] = { @@ -572,6 +588,9 @@ draw_ellipse (const db::DPoint &q1, void draw_ruler_segment (const ant::Object &ruler, size_t index, const db::DCplxTrans &trans, bool sel, lay::CanvasPlane *bitmap, lay::Renderer &renderer) { + bool last_segment = (index == ruler.segments () - 1 || index == std::numeric_limits::max ()); + bool first_segment = (index == 0 || index == std::numeric_limits::max ()); + db::DPoint p1 = ruler.seg_p1 (index), p2 = ruler.seg_p2 (index); // round the starting point, shift both, and round the end point @@ -585,7 +604,7 @@ draw_ruler_segment (const ant::Object &ruler, size_t index, const db::DCplxTrans double mu = double (min_tick_spc) / trans.ctrans (1.0); if (ruler.outline () == Object::OL_diag) { - draw_ruler (q1, q2, lu, mu, sel, q2.x () < q1.x (), ruler.style (), bitmap, renderer); + draw_ruler (q1, q2, lu, mu, sel, q2.x () < q1.x (), ruler.style (), bitmap, renderer, first_segment, last_segment); draw_text (q1, q2, lu, ruler.text (index), q2.x () < q1.x (), ruler.style (), ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); } @@ -595,12 +614,12 @@ draw_ruler_segment (const ant::Object &ruler, size_t index, const db::DCplxTrans bool r = (q2.x () > q1.x ()) ^ (q2.y () < q1.y ()); if (ruler.outline () == Object::OL_diag_xy || ruler.outline () == Object::OL_diag_yx) { - draw_ruler (q1, q2, lu, mu, sel, !r, ruler.style (), bitmap, renderer); + draw_ruler (q1, q2, lu, mu, sel, !r, ruler.style (), bitmap, renderer, first_segment, last_segment); draw_text (q1, q2, lu, ruler.text (index), !r, ruler.style (), ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); } - draw_ruler (q1, db::DPoint (q2.x (), q1.y ()), lu, mu, sel, r, ruler.style (), bitmap, renderer); + draw_ruler (q1, db::DPoint (q2.x (), q1.y ()), lu, mu, sel, r, ruler.style (), bitmap, renderer, false, false); draw_text (q1, db::DPoint (q2.x (), q1.y ()), lu, ruler.text_x (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); - draw_ruler (db::DPoint (q2.x (), q1.y ()), q2, lu, mu, sel, r, ruler.style (), bitmap, renderer); + draw_ruler (db::DPoint (q2.x (), q1.y ()), q2, lu, mu, sel, r, ruler.style (), bitmap, renderer, false, false); draw_text (db::DPoint (q2.x (), q1.y ()), q2, lu, ruler.text_y (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer); } @@ -611,12 +630,12 @@ draw_ruler_segment (const ant::Object &ruler, size_t index, const db::DCplxTrans bool r = (q2.x () > q1.x ()) ^ (q2.y () > q1.y ()); if (ruler.outline () == Object::OL_diag_xy || ruler.outline () == Object::OL_diag_yx) { - draw_ruler (q1, q2, lu, mu, sel, !r, ruler.style (), bitmap, renderer); + draw_ruler (q1, q2, lu, mu, sel, !r, ruler.style (), bitmap, renderer, first_segment, last_segment); draw_text (q1, q2, lu, ruler.text (index), !r, ruler.style (), ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); } - draw_ruler (q1, db::DPoint (q1.x (), q2.y ()), lu, mu, sel, r, ruler.style (), bitmap, renderer); + draw_ruler (q1, db::DPoint (q1.x (), q2.y ()), lu, mu, sel, r, ruler.style (), bitmap, renderer, false, false); draw_text (q1, db::DPoint (q1.x (), q2.y ()), lu, ruler.text_y (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer); - draw_ruler (db::DPoint (q1.x (), q2.y ()), q2, lu, mu, sel, r, ruler.style (), bitmap, renderer); + draw_ruler (db::DPoint (q1.x (), q2.y ()), q2, lu, mu, sel, r, ruler.style (), bitmap, renderer, false, false); draw_text (db::DPoint (q1.x (), q2.y ()), q2, lu, ruler.text_x (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); } @@ -625,12 +644,12 @@ draw_ruler_segment (const ant::Object &ruler, size_t index, const db::DCplxTrans bool r = (q2.x () > q1.x ()) ^ (q2.y () < q1.y ()); - draw_ruler (q1, db::DPoint (q2.x (), q1.y ()), lu, mu, sel, r, ruler.style (), bitmap, renderer); + draw_ruler (q1, db::DPoint (q2.x (), q1.y ()), lu, mu, sel, r, ruler.style (), bitmap, renderer, true, true); draw_text (q1, db::DPoint (q2.x (), q1.y ()), lu, ruler.text_x (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); - draw_ruler (db::DPoint (q2.x (), q1.y ()), q2, lu, mu, sel, r, ruler.style (), bitmap, renderer); + draw_ruler (db::DPoint (q2.x (), q1.y ()), q2, lu, mu, sel, r, ruler.style (), bitmap, renderer, true, true); draw_text (db::DPoint (q2.x (), q1.y ()), q2, lu, ruler.text_y (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer); - draw_ruler (q1, db::DPoint (q1.x (), q2.y ()), lu, mu, sel, !r, ruler.style (), bitmap, renderer); - draw_ruler (db::DPoint (q1.x (), q2.y ()), q2, lu, mu, sel, !r, ruler.style (), bitmap, renderer); + draw_ruler (q1, db::DPoint (q1.x (), q2.y ()), lu, mu, sel, !r, ruler.style (), bitmap, renderer, true, true); + draw_ruler (db::DPoint (q1.x (), q2.y ()), q2, lu, mu, sel, !r, ruler.style (), bitmap, renderer, true, true); draw_text (q1, q2, lu, ruler.text (index), !r, ant::Object::STY_none, ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); } else if (ruler.outline () == Object::OL_ellipse) { @@ -649,18 +668,29 @@ draw_ruler_segment (const ant::Object &ruler, size_t index, const db::DCplxTrans void draw_ruler (const ant::Object &ruler, const db::DCplxTrans &trans, bool sel, lay::CanvasPlane *bitmap, lay::Renderer &renderer) { - for (size_t index = 0; index < ruler.segments (); ++index) { - draw_ruler_segment (ruler, index, trans, sel, bitmap, renderer); + if (ruler.outline () == Object::OL_box || ruler.outline () == Object::OL_ellipse) { + + draw_ruler_segment (ruler, std::numeric_limits::max (), trans, sel, bitmap, renderer); + + } else { + + // other styles support segments, so paint them individually + for (size_t index = 0; index < ruler.segments (); ++index) { + draw_ruler_segment (ruler, index, trans, sel, bitmap, renderer); + } + } } static bool is_selected (const ant::Object &ruler, size_t index, const db::DPoint &pos, double enl, double &distance) { + ant::Object::outline_type outline = ruler.outline (); + db::DPoint p1 = ruler.seg_p1 (index), p2 = ruler.seg_p2 (index); db::DBox b (p1, p2); - if (ruler.outline () == ant::Object::OL_ellipse) { + if (outline == ant::Object::OL_ellipse) { // special handling of the (non-degenerated) ellipse case if (b.height () > 1e-6 && b.width () > 1e-6) { @@ -695,24 +725,24 @@ is_selected (const ant::Object &ruler, size_t index, const db::DPoint &pos, doub db::DEdge edges[4]; unsigned int nedges = 0; - if (ruler.outline () == ant::Object::OL_diag || - ruler.outline () == ant::Object::OL_diag_xy || - ruler.outline () == ant::Object::OL_diag_yx) { - edges [nedges++] = db::DEdge (ruler.p1 (), ruler.p2 ()); + if (outline == ant::Object::OL_diag || + outline == ant::Object::OL_diag_xy || + outline == ant::Object::OL_diag_yx) { + edges [nedges++] = db::DEdge (p1, p2); } - if (ruler.outline () == ant::Object::OL_xy || - ruler.outline () == ant::Object::OL_diag_xy || - ruler.outline () == ant::Object::OL_box || - ruler.outline () == ant::Object::OL_ellipse) { - edges [nedges++] = db::DEdge (ruler.p1 (), db::DPoint (ruler.p2 ().x (), ruler.p1 ().y ())); - edges [nedges++] = db::DEdge (db::DPoint (ruler.p2 ().x (), ruler.p1 ().y ()), ruler.p2 ()); + if (outline == ant::Object::OL_xy || + outline == ant::Object::OL_diag_xy || + outline == ant::Object::OL_box || + outline == ant::Object::OL_ellipse) { + edges [nedges++] = db::DEdge (p1, db::DPoint (p2.x (), p1.y ())); + edges [nedges++] = db::DEdge (db::DPoint (p2.x (), p1.y ()), p2); } - if (ruler.outline () == ant::Object::OL_yx || - ruler.outline () == ant::Object::OL_diag_yx || - ruler.outline () == ant::Object::OL_box || - ruler.outline () == ant::Object::OL_ellipse) { - edges [nedges++] = db::DEdge (ruler.p1 (), db::DPoint (ruler.p1 ().x (), ruler.p2 ().y ())); - edges [nedges++] = db::DEdge (db::DPoint (ruler.p1 ().x (), ruler.p2 ().y ()), ruler.p2 ()); + if (outline == ant::Object::OL_yx || + outline == ant::Object::OL_diag_yx || + outline == ant::Object::OL_box || + outline == ant::Object::OL_ellipse) { + edges [nedges++] = db::DEdge (p1, db::DPoint (p1.x (), p2.y ())); + edges [nedges++] = db::DEdge (db::DPoint (p1.x (), p2.y ()), p2); } for (unsigned int i = 0; i < nedges; ++i) { @@ -729,6 +759,10 @@ is_selected (const ant::Object &ruler, size_t index, const db::DPoint &pos, doub static bool is_selected (const ant::Object &ruler, const db::DPoint &pos, double enl, double &distance) { + if (ruler.outline () == ant::Object::OL_box || ruler.outline () == ant::Object::OL_ellipse) { + return is_selected (ruler, std::numeric_limits::max (), pos, enl, distance); + } + bool any = false; for (size_t index = 0; index < ruler.segments (); ++index) { // NOTE: we check *all* since distance is updated herein. From 8cbe5a235959d4a259616e0292ef2c42c91539ff Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 28 Sep 2022 00:37:37 +0200 Subject: [PATCH 12/54] WIP: debugging, first implementation of new templates --- src/ant/ant/RulerConfigPage4.ui | 20 +++++-- src/ant/ant/antConfig.cc | 8 +++ src/ant/ant/antObject.h | 8 +++ src/ant/ant/antPlugin.cc | 50 ++++++++++++++-- src/ant/ant/antService.cc | 102 ++++++++++++++++++++++++-------- src/ant/ant/antService.h | 6 ++ src/ant/ant/antTemplate.h | 12 +++- src/ant/ant/gsiDeclAnt.cc | 26 +++++++- 8 files changed, 195 insertions(+), 37 deletions(-) diff --git a/src/ant/ant/RulerConfigPage4.ui b/src/ant/ant/RulerConfigPage4.ui index ac380684e..649d18d44 100644 --- a/src/ant/ant/RulerConfigPage4.ui +++ b/src/ant/ant/RulerConfigPage4.ui @@ -89,7 +89,7 @@ ... - + :/up_16px.png:/up_16px.png @@ -103,7 +103,7 @@ ... - + :/add_16px.png:/add_16px.png @@ -117,7 +117,7 @@ ... - + :/del_16px.png:/del_16px.png @@ -131,7 +131,7 @@ ... - + :/down_16px.png:/down_16px.png @@ -833,6 +833,16 @@ Auto measure (points will be set automatically) + + + Angle measurement (three mouse clicks) + + + + + Multi-segment (finish with double click) + + @@ -866,7 +876,7 @@ t_snap_cbx - +
diff --git a/src/ant/ant/antConfig.cc b/src/ant/ant/antConfig.cc index 78356a41b..250945a98 100644 --- a/src/ant/ant/antConfig.cc +++ b/src/ant/ant/antConfig.cc @@ -239,6 +239,10 @@ RulerModeConverter::to_string (ant::Template::ruler_mode_type m) return "single_click"; } else if (m == ant::Template::RulerAutoMetric) { return "auto_metric"; + } else if (m == ant::Template::RulerMultiSegment) { + return "multi_segment"; + } else if (m == ant::Template::RulerAngle) { + return "angle"; } else { return "normal"; } @@ -254,6 +258,10 @@ RulerModeConverter::from_string (const std::string &s, ant::Template::ruler_mode a = ant::Template::RulerSingleClick; } else if (t == "auto_metric") { a = ant::Template::RulerAutoMetric; + } else if (t == "multi_segment") { + a = ant::Template::RulerMultiSegment; + } else if (t == "angle") { + a = ant::Template::RulerAngle; } else { a = ant::Template::RulerNormal; } diff --git a/src/ant/ant/antObject.h b/src/ant/ant/antObject.h index 7304c5bed..60752449d 100644 --- a/src/ant/ant/antObject.h +++ b/src/ant/ant/antObject.h @@ -292,6 +292,14 @@ public: */ void set_points (const point_list &points); + /** + * @brief Sets the ruler's definition points without cleaning + */ + void set_points_exact (const point_list &points) + { + m_points = points; + } + /** * @brief Gets the first point of the indicated segment */ diff --git a/src/ant/ant/antPlugin.cc b/src/ant/ant/antPlugin.cc index fda6d5b8a..74918b24c 100644 --- a/src/ant/ant/antPlugin.cc +++ b/src/ant/ant/antPlugin.cc @@ -68,12 +68,18 @@ static std::vector make_standard_templates () templates.push_back (ant::Template (tl::to_string (tr ("Ruler")), "$X", "$Y", "$D", ant::Object::STY_ruler, ant::Object::OL_diag, true, lay::AC_Global, "_ruler")); + templates.push_back (ant::Template (tl::to_string (tr ("Multi-ruler")), "$X", "$Y", "$D", ant::Object::STY_ruler, ant::Object::OL_diag, true, lay::AC_Global, "_multi_ruler")); + templates.back ().set_mode (ant::Template::RulerMultiSegment); + templates.push_back (ant::Template (tl::to_string (tr ("Cross")), "", "", "$U,$V", ant::Object::STY_cross_both, ant::Object::OL_diag, true, lay::AC_Global, "_cross")); templates.back ().set_mode (ant::Template::RulerSingleClick); templates.push_back (ant::Template (tl::to_string (tr ("Measure")), "$X", "$Y", "$D", ant::Object::STY_ruler, ant::Object::OL_diag, true, lay::AC_Global, "_measure")); templates.back ().set_mode (ant::Template::RulerAutoMetric); + templates.push_back (ant::Template (tl::to_string (tr ("Angle")), "$X", "$Y", "$D", ant::Object::STY_line, ant::Object::OL_diag, true, lay::AC_Global, "_angle")); + templates.back ().set_mode (ant::Template::RulerAngle); + templates.push_back (ant::Template (tl::to_string (tr ("Ellipse")), "W=$(abs(X))", "H=$(abs(Y))", "", ant::Object::STY_line, ant::Object::OL_ellipse, true, lay::AC_Global, std::string ())); templates.push_back (ant::Template (tl::to_string (tr ("Box")), "W=$(abs(X))", "H=$(abs(Y))", "", ant::Object::STY_line, ant::Object::OL_box, true, lay::AC_Global, std::string ())); @@ -197,14 +203,50 @@ PluginDeclaration::initialized (lay::Dispatcher *root) // Check if we already have templates (initial setup) // NOTE: this is not done by using a default value for the configuration item but dynamically. // This provides a migration path from earlier versions (not having templates) to recent ones. - bool any_templates = false; - for (std::vector::iterator i = m_templates.begin (); ! any_templates && i != m_templates.end (); ++i) { - any_templates = ! i->category ().empty (); + std::map cat_names; + for (auto i = m_templates.begin (); i != m_templates.end (); ++i) { + if (! i->category ().empty ()) { + cat_names.insert (std::make_pair (i->category (), i.operator-> ())); + } } - if (! any_templates) { + bool any_missing = false; + auto std_templates = make_standard_templates (); + for (auto t = std_templates.begin (); ! any_missing && t != std_templates.end (); ++t) { + if (! t->category ().empty () && cat_names.find (t->category ()) == cat_names.end ()) { + any_missing = true; + } + } + + if (cat_names.empty ()) { + + // full initial configuration root->config_set (cfg_ruler_templates, ant::TemplatesConverter ().to_string (make_standard_templates ())); root->config_end (); + + } else if (any_missing) { + + // some standard templates are missing - add them now (migration path for later versions) + decltype (m_templates) new_templates; + for (auto t = std_templates.begin (); t != std_templates.end (); ++t) { + if (! t->category ().empty ()) { + auto tt = cat_names.find (t->category ()); + if (tt != cat_names.end ()) { + new_templates.push_back (*tt->second); + } else { + new_templates.push_back (*t); + } + } + } + for (auto i = m_templates.begin (); i != m_templates.end (); ++i) { + if (i->category ().empty ()) { + new_templates.push_back (*i); + } + } + + root->config_set (cfg_ruler_templates, ant::TemplatesConverter ().to_string (new_templates)); + root->config_end (); + } } diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index ffd1e22cb..fb76bc076 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -1560,11 +1560,52 @@ Service::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio return mouse_click_event (p, buttons, prio); } -bool +void +Service::finish_drawing () +{ + // create the ruler object + + // begin the transaction + if (manager ()) { + tl_assert (! manager ()->transacting ()); + manager ()->transaction (tl::to_string (tr ("Create ruler"))); + } + + show_message (); + + insert_ruler (ant::Object (m_current.points (), 0, current_template ()), true); + + // stop dragging + drag_cancel (); + clear_transient_selection (); + + // end the transaction + if (manager ()) { + manager ()->commit (); + } +} + +bool +Service::mouse_double_click_event (const db::DPoint & /*p*/, unsigned int buttons, bool prio) +{ + if (m_drawing && prio && (buttons & lay::LeftButton) != 0) { + + // ends the current ruler (specifically in multi-segment mode) + finish_drawing (); + return true; + + } + + return false; +} + +bool Service::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio) { if (prio && (buttons & lay::LeftButton) != 0) { + const ant::Template &tpl = current_template (); + if (! m_drawing) { // cancel any edit operations so far @@ -1577,8 +1618,6 @@ Service::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio // and clear surplus rulers reduce_rulers (m_max_number_of_rulers - 1); - const ant::Template &tpl = current_template (); - // create and start dragging the ruler if (tpl.mode () == ant::Template::RulerSingleClick) { @@ -1648,7 +1687,13 @@ Service::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio m_p1 = snap1 (p, m_obj_snap && tpl.snap ()).second; - m_current = ant::Object (m_p1, m_p1, 0, tpl); + // NOTE: generating the ruler this way makes sure we have two points + ant::Object::point_list pts; + m_current = ant::Object (pts, 0, tpl); + pts.push_back (m_p1); + pts.push_back (m_p1); + m_current.set_points_exact (pts); + show_message (); if (mp_active_ruler) { @@ -1662,29 +1707,29 @@ Service::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio } + } else if (tpl.mode () == ant::Template::RulerMultiSegment || tpl.mode () == ant::Template::RulerAngle) { + + ant::Object::point_list pts = m_current.points (); + tl_assert (! pts.empty ()); + + if (tpl.mode () == ant::Template::RulerAngle && pts.size () == 3) { + + finish_drawing (); + + } else { + + // add a new point + m_p1 = pts.back (); + + pts.push_back (m_p1); + m_current.set_points_exact (pts); + + } + } else { - // create the ruler object + finish_drawing (); - // begin the transaction - if (manager ()) { - tl_assert (! manager ()->transacting ()); - manager ()->transaction (tl::to_string (tr ("Create ruler"))); - } - - show_message (); - - insert_ruler (ant::Object (m_current.p1 (), m_current.p2 (), 0, current_template ()), true); - - // stop dragging - drag_cancel (); - clear_transient_selection (); - - // end the transaction - if (manager ()) { - manager ()->commit (); - } - } return true; @@ -1731,7 +1776,14 @@ Service::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) set_cursor (lay::Cursor::cross); - m_current.p2 (snap2 (m_p1, p, mp_active_ruler->ruler (), ac_from_buttons (buttons)).second); + // NOTE: we use the direct access path so we do not encounter cleanup by the p1 and p2 setters + // 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; + } + m_current.set_points_exact (pts); + mp_active_ruler->redraw (); show_message (); diff --git a/src/ant/ant/antService.h b/src/ant/ant/antService.h index 6b4c5be9d..5d55af179 100644 --- a/src/ant/ant/antService.h +++ b/src/ant/ant/antService.h @@ -566,6 +566,7 @@ private: virtual bool mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio); virtual bool mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio); virtual bool mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio); + virtual bool mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio); virtual void deactivated (); /** @@ -585,6 +586,11 @@ private: */ void reduce_rulers (int num); + /** + * @brief Finishes drawing mode and creates the ruler + */ + void finish_drawing (); + /** * @brief Delete the selected rulers * diff --git a/src/ant/ant/antTemplate.h b/src/ant/ant/antTemplate.h index 0d8b921f5..6b1bbbf8b 100644 --- a/src/ant/ant/antTemplate.h +++ b/src/ant/ant/antTemplate.h @@ -62,7 +62,17 @@ public: /** * @brief The ruler is auto-metric: a single click will place a ruler and the ruler will extend to the next adjacent structures */ - RulerAutoMetric = 2 + RulerAutoMetric = 2, + + /** + * @brief The ruler an angle type (two segments, three mouse clicks) for angle and circle radius measurements + */ + RulerAngle = 3, + + /** + * @brief The ruler is a multi-segment type + */ + RulerMultiSegment = 4 }; /** diff --git a/src/ant/ant/gsiDeclAnt.cc b/src/ant/ant/gsiDeclAnt.cc index 949cb5c09..24b3d47ca 100644 --- a/src/ant/ant/gsiDeclAnt.cc +++ b/src/ant/ant/gsiDeclAnt.cc @@ -432,6 +432,16 @@ static int ruler_mode_auto_metric () return ant::Template::RulerAutoMetric; } +static int ruler_mode_angle () +{ + return ant::Template::RulerAngle; +} + +static int ruler_mode_multi_segment () +{ + return ant::Template::RulerMultiSegment; +} + static void register_annotation_template (const ant::Object &a, const std::string &title, int mode) { ant::Template t = ant::Template::from_object (a, title, mode); @@ -503,16 +513,28 @@ gsi::Class decl_Annotation (decl_BasicAnnotation, "lay", "Annotat ) + gsi::method ("RulerModeSingleClick", &gsi::ruler_mode_single_click, "@brief Specifies single-click ruler mode for the \\register_template method\n" - "In single click-mode, a ruler can be placed with a single click and p1 will be == p2." + "In single click-mode, a ruler can be placed with a single click and p1 will be == p2.\n" "\n" "This constant has been introduced in version 0.25" ) + gsi::method ("RulerModeAutoMetric", &gsi::ruler_mode_auto_metric, "@brief Specifies auto-metric ruler mode for the \\register_template method\n" - "In auto-metric mode, a ruler can be placed with a single click and p1/p2 will be determined from the neighborhood." + "In auto-metric mode, a ruler can be placed with a single click and p1/p2 will be determined from the neighborhood.\n" "\n" "This constant has been introduced in version 0.25" ) + + gsi::method ("RulerAngle", &gsi::ruler_mode_angle, + "@brief Specifies angle ruler mode for the \\register_template method\n" + "In angle ruler mode, two segments are created for angle and circle radius measurements. Three mouse clicks are required.\n" + "\n" + "This constant has been introduced in version 0.28" + ) + + gsi::method ("RulerMultiSegment", &gsi::ruler_mode_multi_segment, + "@brief Specifies multi-segment mode\n" + "In multi-segment mode, multiple segments can be created. The ruler is finished with a double click.\n" + "\n" + "This constant has been introduced in version 0.28" + ) + gsi::method ("StyleRuler|#style_ruler", &gsi::style_ruler, "@brief Gets the ruler style code for use the \\style method\n" "When this style is specified, the annotation will show a ruler with " From d2d321c35bb6d21db8f4471d7b4a537128cb16ea Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 28 Sep 2022 21:16:47 +0200 Subject: [PATCH 13/54] WIP: point editing of multi-segment rulers --- src/ant/ant/antObject.cc | 84 ++++++++++++++++++++++++++++++++------- src/ant/ant/antObject.h | 25 ++++++++++-- src/ant/ant/antService.cc | 82 ++++++++++++++++++++++---------------- src/ant/ant/antService.h | 2 + 4 files changed, 140 insertions(+), 53 deletions(-) diff --git a/src/ant/ant/antObject.cc b/src/ant/ant/antObject.cc index 1dedd9281..510ce87a3 100644 --- a/src/ant/ant/antObject.cc +++ b/src/ant/ant/antObject.cc @@ -32,6 +32,24 @@ namespace ant { +static void +clean_points_impl (ant::Object::point_list &points) +{ + auto wp = points.begin (); + auto p = points.begin (); + while (p != points.end ()) { + auto pp = p + 1; + while (pp != points.end () && *pp == *p) { + ++pp; + } + *wp++ = *p; + p = pp; + } + + points.erase (wp, points.end ()); +} + + Object::Object () : m_id (-1), m_fmt_x ("$X"), m_fmt_y ("$Y"), m_fmt ("$D"), @@ -88,7 +106,7 @@ Object::Object (const db::DPoint &_p1, const db::DPoint &_p2, int id, const ant: } Object::Object (const Object::point_list &pts, int id, const ant::Template &t) - : m_id (id), + : m_points (pts), m_id (id), m_fmt_x (t.fmt_x ()), m_fmt_y (t.fmt_y ()), m_fmt (t.fmt ()), m_style (t.style ()), m_outline (t.outline ()), m_snap (t.snap ()), m_angle_constraint (t.angle_constraint ()), @@ -98,7 +116,7 @@ Object::Object (const Object::point_list &pts, int id, const ant::Template &t) m_xlabel_xalign (t.xlabel_xalign ()), m_xlabel_yalign (t.xlabel_yalign ()), m_ylabel_xalign (t.ylabel_xalign ()), m_ylabel_yalign (t.ylabel_yalign ()) { - set_points (pts); + clean_points_impl (m_points); } Object::Object (const ant::Object &d) @@ -224,22 +242,36 @@ Object::operator== (const ant::Object &d) const ; } +void +Object::clean_points () +{ + auto new_points = m_points; + clean_points_impl (new_points); + set_points_exact (std::move (new_points)); +} + void Object::set_points (const point_list &points) { - point_list new_points; - auto p = points.begin (); - while (p != points.end ()) { - auto pp = p + 1; - while (pp != points.end () && *pp == *p) { - ++pp; - } - new_points.push_back (*p); - p = pp; - } + auto new_points = points; + clean_points_impl (new_points); + set_points_exact (std::move (new_points)); +} - if (m_points != new_points) { - m_points = new_points; +void +Object::set_points_exact (const point_list &points) +{ + if (m_points != points) { + m_points = points; + property_changed (); + } +} + +void +Object::set_points_exact (point_list &&points) +{ + if (m_points != points) { + m_points.swap (points); property_changed (); } } @@ -272,6 +304,30 @@ Object::seg_p2 (size_t seg_index) const } } +void +Object::seg_p1 (size_t seg_index, const db::DPoint &p) +{ + if (seg_index == std::numeric_limits::max ()) { + p1 (p); + } else if (seg_index < m_points.size ()) { + m_points[seg_index] = p; + } else if (! m_points.empty ()) { + m_points.back () = p; + } +} + +void +Object::seg_p2 (size_t seg_index, const db::DPoint &p) +{ + if (seg_index == std::numeric_limits::max ()) { + p2 (p); + } else if (seg_index + 1 < m_points.size ()) { + m_points[seg_index + 1] = p; + } else if (! m_points.empty ()) { + m_points.back () = p; + } +} + void Object::p1 (const db::DPoint &p) { diff --git a/src/ant/ant/antObject.h b/src/ant/ant/antObject.h index 60752449d..6fd478c7a 100644 --- a/src/ant/ant/antObject.h +++ b/src/ant/ant/antObject.h @@ -295,10 +295,17 @@ public: /** * @brief Sets the ruler's definition points without cleaning */ - void set_points_exact (const point_list &points) - { - m_points = points; - } + void set_points_exact (const point_list &points); + + /** + * @brief Sets the ruler's definition points without cleaning (move semantics) + */ + void set_points_exact (point_list &&points); + + /** + * @brief Cleans the point list + */ + void clean_points (); /** * @brief Gets the first point of the indicated segment @@ -310,6 +317,16 @@ public: */ db::DPoint seg_p2 (size_t seg_index) const; + /** + * @brief Sets the first point of the indicated segment + */ + void seg_p1 (size_t seg_index, const db::DPoint &p); + + /** + * @brief Sets the second point of the indicated segment + */ + void seg_p2 (size_t seg_index, const db::DPoint &p); + /** * @brief Gets the number of segments * diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index fb76bc076..ffcf88a06 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -858,6 +858,7 @@ Service::Service (db::Manager *manager, lay::LayoutViewBase *view) mp_transient_ruler (0), m_drawing (false), m_current (), m_move_mode (MoveNone), + m_seg_index (0), m_current_template (0) { mp_view->annotations_changed_event.add (this, &Service::annotations_changed); @@ -1074,29 +1075,28 @@ Service::insert_ruler (const ant::Object &ruler, bool limit_number) return new_id; } -/** - * @brief Helper function to determine which move mode to choose given a certain search box and ant::Object - */ static bool -dragging_what (const ant::Object *robj, const db::DBox &search_dbox, ant::Service::MoveMode &mode, db::DPoint &p1) +dragging_what_seg (const ant::Object *robj, const db::DBox &search_dbox, ant::Service::MoveMode &mode, db::DPoint &p1, size_t index) { + ant::Object::outline_type outline = robj->outline (); + db::DPoint p12, p21; bool has_p12 = false, has_p21 = false; - db::DPoint p11 = robj->p1 (), p22 = robj->p2 (); + db::DPoint p11 = robj->seg_p1 (index), p22 = robj->seg_p2 (index); db::DPoint c = p11 + (p22 - p11) * 0.5; - - if (robj->outline () == ant::Object::OL_xy || robj->outline () == ant::Object::OL_diag_xy || robj->outline () == ant::Object::OL_box) { - p12 = db::DPoint (robj->p2 ().x (), robj->p1 ().y ()); + + if (outline == ant::Object::OL_xy || outline== ant::Object::OL_diag_xy || outline == ant::Object::OL_box) { + p12 = db::DPoint (p22.x (), p11.y ()); has_p12 = true; } - if (robj->outline () == ant::Object::OL_yx || robj->outline () == ant::Object::OL_diag_yx || robj->outline () == ant::Object::OL_box) { - p21 = db::DPoint (robj->p1 ().x (), robj->p2 ().y ()); + if (outline == ant::Object::OL_yx || outline == ant::Object::OL_diag_yx || outline == ant::Object::OL_box) { + p21 = db::DPoint (p11.x (), p22.y ()); has_p21 = true; } - - if (robj->outline () == ant::Object::OL_ellipse) { + + if (outline == ant::Object::OL_ellipse) { db::DVector d = (p22 - p11) * 0.5; p12 = c + db::DVector (d.x (), -d.y ()); p21 = c + db::DVector (-d.x (), d.y ()); @@ -1106,7 +1106,7 @@ dragging_what (const ant::Object *robj, const db::DBox &search_dbox, ant::Servic // HINT: this was implemented returning a std::pair, but // I was not able to get it to work in gcc 4.1.2 in -O3 mode ... - + if (search_dbox.contains (p11)) { p1 = p11; mode = ant::Service::MoveP1; @@ -1147,18 +1147,28 @@ dragging_what (const ant::Object *robj, const db::DBox &search_dbox, ant::Servic mode = ant::Service::MoveP2Y; return true; } - if ((robj->outline () == ant::Object::OL_diag || robj->outline () == ant::Object::OL_diag_xy || robj->outline () == ant::Object::OL_diag_yx) - && db::DEdge (p11, p22).distance_abs (search_dbox.center ()) <= search_dbox.width () * 0.5) { - p1 = search_dbox.center (); - mode = ant::Service::MoveRuler; - return true; + + return false; +} + +/** + * @brief Helper function to determine which move mode to choose given a certain search box and ant::Object + */ +static bool +dragging_what (const ant::Object *robj, const db::DBox &search_dbox, ant::Service::MoveMode &mode, db::DPoint &p1, size_t &index) +{ + ant::Object::outline_type outline = robj->outline (); + + if (outline == ant::Object::OL_box || outline == ant::Object::OL_ellipse) { + index = std::numeric_limits::max (); + return dragging_what_seg (robj, search_dbox, mode, p1, index); } - if ((robj->outline () == ant::Object::OL_box || robj->outline () == ant::Object::OL_ellipse) && search_dbox.inside (db::DBox (p11, p22))) { - p1 = search_dbox.center (); - mode = ant::Service::MoveRuler; - return true; + + for (index = 0; index < robj->segments (); ++index) { + if (dragging_what_seg (robj, search_dbox, mode, p1, index)) { + return true; + } } - return false; } @@ -1185,6 +1195,7 @@ Service::begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::ang } else if (mode == lay::Editable::Partial) { m_move_mode = MoveNone; + m_seg_index = 0; // compute search box double l = catch_distance (); @@ -1216,7 +1227,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_move_mode != MoveRuler) { + if (dragging_what (robj, search_dbox, m_move_mode, m_p1, m_seg_index) && m_move_mode != MoveRuler) { // found anything: make the moved ruler the selection clear_selection (); @@ -1273,7 +1284,7 @@ Service::begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::ang const ant::Object *robj = dynamic_cast ((*r).ptr ()); if (robj && (! robj_min || robj == robj_min)) { - if (dragging_what (robj, search_dbox, m_move_mode, m_p1)) { + if (dragging_what (robj, search_dbox, m_move_mode, m_p1, m_seg_index)) { // found anything: make the moved ruler the selection clear_selection (); @@ -1340,50 +1351,50 @@ Service::move (const db::DPoint &p, lay::angle_constraint_type ac) if (m_move_mode == MoveP1) { - m_current.p1 (snap2 (m_p1, p, &m_current, ac).second); + m_current.seg_p1 (m_seg_index, snap2 (m_p1, p, &m_current, ac).second); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP2) { - m_current.p2 (snap2 (m_p1, p, &m_current, ac).second); + m_current.seg_p2 (m_seg_index, snap2 (m_p1, p, &m_current, ac).second); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP12) { db::DPoint p12 = snap2 (m_p1, p, &m_current, ac).second; - m_current.p1 (db::DPoint (m_current.p1 ().x(), p12.y ())); - m_current.p2 (db::DPoint (p12.x (), m_current.p2 ().y ())); + m_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; - m_current.p1 (db::DPoint (p21.x (), m_current.p1 ().y ())); - m_current.p2 (db::DPoint (m_current.p2 ().x(), p21.y ())); + 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; - m_current.p1 (db::DPoint (pc.x (), m_current.p1 ().y ())); + 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; - m_current.p2 (db::DPoint (pc.x (), m_current.p2 ().y ())); + 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; - m_current.p1 (db::DPoint (m_current.p1 ().x (), pc.y ())); + 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; - m_current.p2 (db::DPoint (m_current.p2 ().x (), pc.y ())); + 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) { @@ -1474,6 +1485,7 @@ Service::end_move (const db::DPoint &, lay::angle_constraint_type) } else if (m_move_mode != MoveNone) { // replace the ruler that was moved + m_current.clean_points (); mp_view->annotation_shapes ().replace (m_selected.begin ()->first, db::DUserObject (new ant::Object (m_current))); annotation_changed_event (m_current.id ()); diff --git a/src/ant/ant/antService.h b/src/ant/ant/antService.h index 5d55af179..a36bf6884 100644 --- a/src/ant/ant/antService.h +++ b/src/ant/ant/antService.h @@ -545,6 +545,8 @@ private: ant::Object m_original; // The current move mode MoveMode m_move_mode; + // The currently moving segment + size_t m_seg_index; // The ruler template std::vector m_ruler_templates; unsigned int m_current_template; From 8415e075ada329563b485c81e1ff2fa5b064ab30 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 28 Sep 2022 23:58:34 +0200 Subject: [PATCH 14/54] WIP: angle measurement ruler, radius measurement one. Needs improvement --- src/ant/ant/RulerConfigPage4.ui | 10 ++ src/ant/ant/RulerPropertiesPage.ui | 10 ++ src/ant/ant/antConfig.cc | 12 +- src/ant/ant/antObject.h | 4 +- src/ant/ant/antPlugin.cc | 7 +- src/ant/ant/antService.cc | 278 +++++++++++++++++++++++------ src/ant/ant/antTemplate.h | 2 +- src/ant/ant/gsiDeclAnt.cc | 66 ++++--- 8 files changed, 306 insertions(+), 83 deletions(-) diff --git a/src/ant/ant/RulerConfigPage4.ui b/src/ant/ant/RulerConfigPage4.ui index 649d18d44..7e2c2365c 100644 --- a/src/ant/ant/RulerConfigPage4.ui +++ b/src/ant/ant/RulerConfigPage4.ui @@ -574,6 +574,16 @@ Ellipse + + + Angle measurement + + + + + Radius measurement + + diff --git a/src/ant/ant/RulerPropertiesPage.ui b/src/ant/ant/RulerPropertiesPage.ui index 567287ddc..28451b680 100644 --- a/src/ant/ant/RulerPropertiesPage.ui +++ b/src/ant/ant/RulerPropertiesPage.ui @@ -352,6 +352,16 @@ Ellipse + + + Angle measurement + + + + + Radius measurement + + diff --git a/src/ant/ant/antConfig.cc b/src/ant/ant/antConfig.cc index 250945a98..ed5230023 100644 --- a/src/ant/ant/antConfig.cc +++ b/src/ant/ant/antConfig.cc @@ -136,6 +136,10 @@ OutlineConverter::to_string (ant::Object::outline_type o) return "box"; } else if (o == ant::Object::OL_ellipse) { return "ellipse"; + } else if (o == ant::Object::OL_radius) { + return "radius"; + } else if (o == ant::Object::OL_angle) { + return "angle"; } else { return ""; } @@ -159,6 +163,10 @@ OutlineConverter::from_string (const std::string &s, ant::Object::outline_type & o = ant::Object::OL_box; } else if (t == "ellipse") { o = ant::Object::OL_ellipse; + } else if (t == "radius") { + o = ant::Object::OL_radius; + } else if (t == "angle") { + o = ant::Object::OL_angle; } else { o = ant::Object::OL_diag; } @@ -241,7 +249,7 @@ RulerModeConverter::to_string (ant::Template::ruler_mode_type m) return "auto_metric"; } else if (m == ant::Template::RulerMultiSegment) { return "multi_segment"; - } else if (m == ant::Template::RulerAngle) { + } else if (m == ant::Template::RulerThreeClicks) { return "angle"; } else { return "normal"; @@ -261,7 +269,7 @@ RulerModeConverter::from_string (const std::string &s, ant::Template::ruler_mode } else if (t == "multi_segment") { a = ant::Template::RulerMultiSegment; } else if (t == "angle") { - a = ant::Template::RulerAngle; + a = ant::Template::RulerThreeClicks; } else { a = ant::Template::RulerNormal; } diff --git a/src/ant/ant/antObject.h b/src/ant/ant/antObject.h index 6fd478c7a..e225ce593 100644 --- a/src/ant/ant/antObject.h +++ b/src/ant/ant/antObject.h @@ -77,8 +77,10 @@ public: * OL_diag_yx: both OL_diag and OL_yx * OL_box: draw a box defined by start and end point * OL_ellipse: draws an ellipse with p1 and p2 defining the extension (style is ignored) + * OL_angle: an angle measurement ruler (first vs. last segment) + * OL_radius: a radius measurement ruler */ - enum outline_type { OL_diag = 0, OL_xy = 1, OL_diag_xy = 2, OL_yx = 3, OL_diag_yx = 4, OL_box = 5, OL_ellipse = 6 }; + enum outline_type { OL_diag = 0, OL_xy = 1, OL_diag_xy = 2, OL_yx = 3, OL_diag_yx = 4, OL_box = 5, OL_ellipse = 6, OL_angle = 7, OL_radius = 8 }; /** * @brief The position type of the main label diff --git a/src/ant/ant/antPlugin.cc b/src/ant/ant/antPlugin.cc index 74918b24c..7175b11c9 100644 --- a/src/ant/ant/antPlugin.cc +++ b/src/ant/ant/antPlugin.cc @@ -77,8 +77,11 @@ static std::vector make_standard_templates () templates.push_back (ant::Template (tl::to_string (tr ("Measure")), "$X", "$Y", "$D", ant::Object::STY_ruler, ant::Object::OL_diag, true, lay::AC_Global, "_measure")); templates.back ().set_mode (ant::Template::RulerAutoMetric); - templates.push_back (ant::Template (tl::to_string (tr ("Angle")), "$X", "$Y", "$D", ant::Object::STY_line, ant::Object::OL_diag, true, lay::AC_Global, "_angle")); - templates.back ().set_mode (ant::Template::RulerAngle); + templates.push_back (ant::Template (tl::to_string (tr ("Angle")), "$X", "$Y", "$D", ant::Object::STY_line, ant::Object::OL_angle, true, lay::AC_Global, "_angle")); + templates.back ().set_mode (ant::Template::RulerThreeClicks); + + templates.push_back (ant::Template (tl::to_string (tr ("Radius")), "$X", "$Y", "$D", ant::Object::STY_line, ant::Object::OL_radius, true, lay::AC_Global, "_radius")); + templates.back ().set_mode (ant::Template::RulerThreeClicks); templates.push_back (ant::Template (tl::to_string (tr ("Ellipse")), "W=$(abs(X))", "H=$(abs(Y))", "", ant::Object::STY_line, ant::Object::OL_ellipse, true, lay::AC_Global, std::string ())); diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index ffcf88a06..cee1d246e 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -503,9 +503,12 @@ draw_text (const db::DPoint &q1, * * @param q1 The first point in pixel space * @param q2 The second point in pixel space + * @param length_u The "typical dimension" - used to simplify for very small ellipses * @param sel True to draw ruler in "selected" mode * @param bitmap The bitmap to draw the ruler on * @param renderer The renderer object + * @param start_angle The starting angle (in radians) + * @param stop_angle The stop angle (in radians) */ void draw_ellipse (const db::DPoint &q1, @@ -513,7 +516,9 @@ draw_ellipse (const db::DPoint &q1, double length_u, bool sel, lay::CanvasPlane *bitmap, - lay::Renderer &renderer) + lay::Renderer &renderer, + double start_angle = 0.0, + double stop_angle = 2.0 * M_PI) { double sel_width = 2 / renderer.resolution (); @@ -532,7 +537,7 @@ draw_ellipse (const db::DPoint &q1, } else { - int npoints = 200; + int npoints = int (floor (200 * abs (stop_angle - start_angle) / (2.0 * M_PI))); // produce polygon stuff @@ -540,43 +545,29 @@ draw_ellipse (const db::DPoint &q1, double ry = fabs ((q2 - q1).y () * 0.5); db::DPoint c = q1 + (q2 - q1) * 0.5; - db::DPolygon p; - - std::vector pts; - pts.reserve (npoints); - if (sel) { rx += sel_width * 0.5; ry += sel_width * 0.5; } - double da = M_PI * 2.0 / double (npoints); - for (int i = 0; i < npoints; ++i) { - double a = da * i; + std::vector pts; + pts.reserve (npoints + 1); + + double da = fabs (stop_angle - start_angle) / double (npoints); + for (int i = 0; i < npoints + 1; ++i) { + double a = da * i + start_angle; pts.push_back (c + db::DVector (rx * cos (a), ry * sin (a))); } - p.assign_hull (pts.begin (), pts.end ()); - if (sel) { - pts.clear (); - - rx -= sel_width; - ry -= sel_width; - for (int i = 0; i < npoints; ++i) { - double a = da * i; - pts.push_back (c + db::DVector (rx * cos (a), ry * sin (a))); - } - - p.insert_hole (pts.begin (), pts.end ()); - + db::DPath p (pts.begin (), pts.end (), sel_width); renderer.draw (p, bitmap, bitmap, 0, 0); } else { - for (db::DPolygon::polygon_edge_iterator e = p.begin_edge (); ! e.at_end (); ++e) { - renderer.draw (*e, 0, bitmap, 0, 0); + for (size_t i = 0; i + 1 < pts.size (); ++i) { + renderer.draw (db::DEdge (pts [i], pts [i + 1]), 0, bitmap, 0, 0); } } @@ -639,52 +630,236 @@ draw_ruler_segment (const ant::Object &ruler, size_t index, const db::DCplxTrans draw_text (db::DPoint (q1.x (), q2.y ()), q2, lu, ruler.text_x (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); } +} - if (ruler.outline () == Object::OL_box) { +void +draw_ruler_box (const ant::Object &ruler, const db::DCplxTrans &trans, bool sel, lay::CanvasPlane *bitmap, lay::Renderer &renderer) +{ + db::DPoint p1 = ruler.p1 (), p2 = ruler.p2 (); - bool r = (q2.x () > q1.x ()) ^ (q2.y () < q1.y ()); + // round the starting point, shift both, and round the end point + std::pair v = lay::snap (trans * p1, trans * p2); + db::DPoint q1 = v.first; + db::DPoint q2 = v.second; - draw_ruler (q1, db::DPoint (q2.x (), q1.y ()), lu, mu, sel, r, ruler.style (), bitmap, renderer, true, true); - draw_text (q1, db::DPoint (q2.x (), q1.y ()), lu, ruler.text_x (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); - draw_ruler (db::DPoint (q2.x (), q1.y ()), q2, lu, mu, sel, r, ruler.style (), bitmap, renderer, true, true); - draw_text (db::DPoint (q2.x (), q1.y ()), q2, lu, ruler.text_y (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer); - draw_ruler (q1, db::DPoint (q1.x (), q2.y ()), lu, mu, sel, !r, ruler.style (), bitmap, renderer, true, true); - draw_ruler (db::DPoint (q1.x (), q2.y ()), q2, lu, mu, sel, !r, ruler.style (), bitmap, renderer, true, true); - draw_text (q1, q2, lu, ruler.text (index), !r, ant::Object::STY_none, ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); + double lu = p1.double_distance (p2); + int min_tick_spc = int (0.5 + 20 / renderer.resolution ()); // min tick spacing in canvas units + double mu = double (min_tick_spc) / trans.ctrans (1.0); - } else if (ruler.outline () == Object::OL_ellipse) { + bool r = (q2.x () > q1.x ()) ^ (q2.y () < q1.y ()); - bool r = (q2.x () > q1.x ()) ^ (q2.y () < q1.y ()); + size_t index = std::numeric_limits::max (); + draw_ruler (q1, db::DPoint (q2.x (), q1.y ()), lu, mu, sel, r, ruler.style (), bitmap, renderer, true, true); + draw_text (q1, db::DPoint (q2.x (), q1.y ()), lu, ruler.text_x (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); + draw_ruler (db::DPoint (q2.x (), q1.y ()), q2, lu, mu, sel, r, ruler.style (), bitmap, renderer, true, true); + draw_text (db::DPoint (q2.x (), q1.y ()), q2, lu, ruler.text_y (index, trans.fp_trans ()), r, ruler.style (), ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer); + draw_ruler (q1, db::DPoint (q1.x (), q2.y ()), lu, mu, sel, !r, ruler.style (), bitmap, renderer, true, true); + draw_ruler (db::DPoint (q1.x (), q2.y ()), q2, lu, mu, sel, !r, ruler.style (), bitmap, renderer, true, true); + draw_text (q1, q2, lu, ruler.text (index), !r, ant::Object::STY_none, ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); +} - draw_text (q1, db::DPoint (q2.x (), q1.y ()), lu, ruler.text_x (index, trans.fp_trans ()), r, ant::Object::STY_none, ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); - draw_text (db::DPoint (q2.x (), q1.y ()), q2, lu, ruler.text_y (index, trans.fp_trans ()), r, ant::Object::STY_none, ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer); - draw_text (q1, q2, lu, ruler.text (index), !r, ant::Object::STY_none, ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); +void +draw_ruler_ellipse (const ant::Object &ruler, const db::DCplxTrans &trans, bool sel, lay::CanvasPlane *bitmap, lay::Renderer &renderer) +{ + db::DPoint p1 = ruler.p1 (), p2 = ruler.p2 (); - draw_ellipse (q1, q2, lu, sel, bitmap, renderer); + // round the starting point, shift both, and round the end point + std::pair v = lay::snap (trans * p1, trans * p2); + db::DPoint q1 = v.first; + db::DPoint q2 = v.second; + + double lu = p1.double_distance (p2); + + bool r = (q2.x () > q1.x ()) ^ (q2.y () < q1.y ()); + + size_t index = std::numeric_limits::max (); + draw_text (q1, db::DPoint (q2.x (), q1.y ()), lu, ruler.text_x (index, trans.fp_trans ()), r, ant::Object::STY_none, ant::Object::POS_center, ruler.xlabel_xalign (), ruler.xlabel_yalign (), bitmap, renderer); + draw_text (db::DPoint (q2.x (), q1.y ()), q2, lu, ruler.text_y (index, trans.fp_trans ()), r, ant::Object::STY_none, ant::Object::POS_center, ruler.ylabel_xalign (), ruler.ylabel_yalign (), bitmap, renderer); + draw_text (q1, q2, lu, ruler.text (index), !r, ant::Object::STY_none, ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); + + draw_ellipse (q1, q2, lu, sel, bitmap, renderer); +} + +bool +compute_interpolating_circle (const ant::Object::point_list &points, double &radius, db::DPoint ¢er, double &start_angle, double &stop_angle) +{ + if (points.size () < 2) { + return false; + } + + double d = points.back ().distance (points.front ()) * 0.5; + if (d < db::epsilon) { + return false; + } + + db::DVector n = points.back () - points.front (); + db::DPoint m = points.front () + n * 0.5; + n = db::DVector (n.y (), -n.x ()) * (1.0 / d); + + double nom = 0.0; + double div = 0.0; + + for (size_t i = 1; i + 1 < points.size (); ++i) { + db::DVector p = points [i] - m; + double pn = db::sprod (p, n); + div += pn * pn; + nom += pn * (p.sq_double_length () - d * d); + } + + if (div < db::epsilon) { + return false; + } + + double l = 0.5 * nom / div; + radius = sqrt (l * l + d * d); + center = m + n * l; + + double a = atan2 (-n.y (), -n.x ()); + double da = atan2 (d, l); + + if (fabs (l) < db::epsilon) { + + start_angle = 0.0; + stop_angle = M_PI * 2.0; + + } else if (l < 0.0) { + + start_angle = a + da; + stop_angle = start_angle + 2.0 * (M_PI - da); + + } else { + + start_angle = a - da; + stop_angle = a + da; + + } + + while (stop_angle < start_angle - db::epsilon) { + stop_angle += M_PI * 2.0; + } + + return true; +} + +void +draw_ruler_radius (const ant::Object &ruler, const db::DCplxTrans &trans, bool sel, lay::CanvasPlane *bitmap, lay::Renderer &renderer) +{ + // draw crosses for the support points + for (auto p = ruler.points ().begin (); p != ruler.points ().end (); ++p) { + ant::Object supp (*p, *p, 0, std::string (), std::string (), std::string (), ant::Object::STY_cross_start, ant::Object::OL_diag, false, lay::AC_Global); + draw_ruler_segment (supp, 0, trans, sel, bitmap, renderer); + } + + double radius = 0.0; + double start_angle = 0.0, stop_angle = 0.0; + db::DPoint center; + + // circle interpolation + if (compute_interpolating_circle (ruler.points (), radius, center, start_angle, stop_angle)) { + + // draw circle segment + db::DVector rr (radius, radius); + std::pair v = lay::snap (trans * (center - rr), trans * (center + rr)); + draw_ellipse (v.first, v.second, radius * 2.0, sel, bitmap, renderer, start_angle, stop_angle); + + double a = 0.5 * (start_angle + stop_angle); + db::DPoint rc = center + db::DVector (cos (a), sin (a)) * radius; + + // draw a center marker + ant::Object center_loc (center, center, 0, std::string (), ruler.fmt_x (), ruler.fmt_y (), ant::Object::STY_cross_start, ant::Object::OL_diag, false, lay::AC_Global); + draw_ruler_segment (center_loc, 0, trans, sel, bitmap, renderer); + + // draw the radius ruler + ant::Object radius (center, rc, 0, ruler.fmt (), std::string (), std::string (), ruler.style (), ruler.outline (), false, lay::AC_Global); + draw_ruler_segment (radius, 0, trans, sel, bitmap, renderer); } } +void +draw_ruler_angle (const ant::Object &ruler, const db::DCplxTrans &trans, bool sel, lay::CanvasPlane *bitmap, lay::Renderer &renderer) +{ + // draw segments in diag mode + ant::Object basic = ruler; + basic.outline (ant::Object::OL_diag); + draw_ruler_segment (basic, 0, trans, sel, bitmap, renderer); + if (basic.segments () > 1) { + draw_ruler_segment (basic, basic.segments () - 1, trans, sel, bitmap, renderer); + } + + if (ruler.points ().size () < 3) { + return; + } + + db::DPoint p1 = ruler.p1 (), p2 = ruler.p2 (); + + db::DVector pc; + for (size_t i = 1; i + 1 < ruler.points ().size (); ++i) { + pc += ruler.points ()[i] - db::DPoint (); + } + db::DPoint center = db::DPoint () + pc * (1.0 / double (ruler.points ().size () - 2)); + + db::DVector v1 (p1 - center); + if (v1.double_length () < db::epsilon) { + return; + } + + db::DVector v2 (p2 - center); + if (v2.double_length () < db::epsilon) { + return; + } + + double radius = std::min (v1.double_length (), v2.double_length ()); + + v1 *= 1.0 / v1.double_length (); + v2 *= 1.0 / v2.double_length (); + + if (db::vprod_sign (v1, v2) == 0) { + return; + } + + double start_angle = 0.0, stop_angle = 0.0; + start_angle = atan2 (v1.y (), v1.x ()); + stop_angle = atan2 (v2.y (), v2.x ()); + + if (db::vprod_sign (v1, v2) < 0) { + std::swap (start_angle, stop_angle); + } + + while (stop_angle < start_angle - db::epsilon) { + stop_angle += M_PI * 2.0; + } + + db::DVector rr (radius * 0.9, radius * 0.9); + std::pair v = lay::snap (trans * (center - rr), trans * (center + rr)); + draw_ellipse (v.first, v.second, radius * 2.0, sel, bitmap, renderer, start_angle, stop_angle); + + // @@@ TODO: draw text, apply styles to circle segments? ... +} + void draw_ruler (const ant::Object &ruler, const db::DCplxTrans &trans, bool sel, lay::CanvasPlane *bitmap, lay::Renderer &renderer) { - if (ruler.outline () == Object::OL_box || ruler.outline () == Object::OL_ellipse) { - - draw_ruler_segment (ruler, std::numeric_limits::max (), trans, sel, bitmap, renderer); - + if (ruler.outline () == Object::OL_box) { + draw_ruler_box (ruler, trans, sel, bitmap, renderer); + } else if (ruler.outline () == Object::OL_ellipse) { + draw_ruler_ellipse (ruler, trans, sel, bitmap, renderer); + } else if (ruler.outline () == Object::OL_angle) { + draw_ruler_angle (ruler, trans, sel, bitmap, renderer); + } else if (ruler.outline () == Object::OL_radius) { + draw_ruler_radius (ruler, trans, sel, bitmap, renderer); } else { - - // other styles support segments, so paint them individually + // other outline styles support segments, so paint them individually for (size_t index = 0; index < ruler.segments (); ++index) { draw_ruler_segment (ruler, index, trans, sel, bitmap, renderer); } - } } static bool is_selected (const ant::Object &ruler, size_t index, const db::DPoint &pos, double enl, double &distance) { +// @@@ angle, radius ant::Object::outline_type outline = ruler.outline (); db::DPoint p1 = ruler.seg_p1 (index), p2 = ruler.seg_p2 (index); @@ -759,7 +934,7 @@ is_selected (const ant::Object &ruler, size_t index, const db::DPoint &pos, doub static bool is_selected (const ant::Object &ruler, const db::DPoint &pos, double enl, double &distance) { - if (ruler.outline () == ant::Object::OL_box || ruler.outline () == ant::Object::OL_ellipse) { + if (ruler.outline () == ant::Object::OL_box || ruler.outline () == ant::Object::OL_ellipse || ruler.outline () == ant::Object::OL_angle || ruler.outline () == ant::Object::OL_radius) { return is_selected (ruler, std::numeric_limits::max (), pos, enl, distance); } @@ -1078,6 +1253,7 @@ Service::insert_ruler (const ant::Object &ruler, bool limit_number) static bool dragging_what_seg (const ant::Object *robj, const db::DBox &search_dbox, ant::Service::MoveMode &mode, db::DPoint &p1, size_t index) { +// @@@ radius, angle ant::Object::outline_type outline = robj->outline (); db::DPoint p12, p21; @@ -1159,7 +1335,7 @@ dragging_what (const ant::Object *robj, const db::DBox &search_dbox, ant::Servic { ant::Object::outline_type outline = robj->outline (); - if (outline == ant::Object::OL_box || outline == ant::Object::OL_ellipse) { + if (outline == ant::Object::OL_box || outline == ant::Object::OL_ellipse || outline == ant::Object::OL_angle || outline == ant::Object::OL_radius) { index = std::numeric_limits::max (); return dragging_what_seg (robj, search_dbox, mode, p1, index); } @@ -1719,12 +1895,12 @@ Service::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio } - } else if (tpl.mode () == ant::Template::RulerMultiSegment || tpl.mode () == ant::Template::RulerAngle) { + } else if (tpl.mode () == ant::Template::RulerMultiSegment || tpl.mode () == ant::Template::RulerThreeClicks) { ant::Object::point_list pts = m_current.points (); tl_assert (! pts.empty ()); - if (tpl.mode () == ant::Template::RulerAngle && pts.size () == 3) { + if (tpl.mode () == ant::Template::RulerThreeClicks && pts.size () == 3) { finish_drawing (); diff --git a/src/ant/ant/antTemplate.h b/src/ant/ant/antTemplate.h index 6b1bbbf8b..ab561a5e8 100644 --- a/src/ant/ant/antTemplate.h +++ b/src/ant/ant/antTemplate.h @@ -67,7 +67,7 @@ public: /** * @brief The ruler an angle type (two segments, three mouse clicks) for angle and circle radius measurements */ - RulerAngle = 3, + RulerThreeClicks = 3, /** * @brief The ruler is a multi-segment type diff --git a/src/ant/ant/gsiDeclAnt.cc b/src/ant/ant/gsiDeclAnt.cc index 24b3d47ca..032190b48 100644 --- a/src/ant/ant/gsiDeclAnt.cc +++ b/src/ant/ant/gsiDeclAnt.cc @@ -48,6 +48,8 @@ static int outline_yx () { return int (ant::Object::OL_yx); } static int outline_diag_yx () { return int (ant::Object::OL_diag_yx); } static int outline_box () { return int (ant::Object::OL_box); } static int outline_ellipse () { return int (ant::Object::OL_ellipse); } +static int outline_angle () { return int (ant::Object::OL_angle); } +static int outline_radius () { return int (ant::Object::OL_radius); } static int angle_any () { return int (lay::AC_Any); } static int angle_diagonal () { return int (lay::AC_Diagonal); } @@ -432,9 +434,9 @@ static int ruler_mode_auto_metric () return ant::Template::RulerAutoMetric; } -static int ruler_mode_angle () +static int ruler_mode_three_clicks () { - return ant::Template::RulerAngle; + return ant::Template::RulerThreeClicks; } static int ruler_mode_multi_segment () @@ -523,9 +525,9 @@ gsi::Class decl_Annotation (decl_BasicAnnotation, "lay", "Annotat "\n" "This constant has been introduced in version 0.25" ) + - gsi::method ("RulerAngle", &gsi::ruler_mode_angle, - "@brief Specifies angle ruler mode for the \\register_template method\n" - "In angle ruler mode, two segments are created for angle and circle radius measurements. Three mouse clicks are required.\n" + gsi::method ("RulerThreeClicks", &gsi::ruler_mode_three_clicks, + "@brief Specifies three-click ruler mode for the \\register_template method\n" + "In this ruler mode, two segments are created for angle and circle radius measurements. Three mouse clicks are required.\n" "\n" "This constant has been introduced in version 0.28" ) + @@ -535,58 +537,58 @@ gsi::Class decl_Annotation (decl_BasicAnnotation, "lay", "Annotat "\n" "This constant has been introduced in version 0.28" ) + - gsi::method ("StyleRuler|#style_ruler", &gsi::style_ruler, + gsi::method ("StyleRuler", &gsi::style_ruler, "@brief Gets the ruler style code for use the \\style method\n" "When this style is specified, the annotation will show a ruler with " "some ticks at distances indicating a decade of units and a suitable " "subdivision into minor ticks at intervals of 1, 2 or 5 units." ) + - gsi::method ("StyleArrowEnd|#style_arrow_end", &gsi::style_arrow_end, + gsi::method ("StyleArrowEnd", &gsi::style_arrow_end, "@brief Gets the end arrow style code for use the \\style method\n" "When this style is specified, an arrow is drawn pointing from the start to the end point." ) + - gsi::method ("StyleArrowStart|#style_arrow_start", &gsi::style_arrow_start, + gsi::method ("StyleArrowStart", &gsi::style_arrow_start, "@brief Gets the start arrow style code for use the \\style method\n" "When this style is specified, an arrow is drawn pointing from the end to the start point." ) + - gsi::method ("StyleArrowBoth|#style_arrow_both", &gsi::style_arrow_both, + gsi::method ("StyleArrowBoth", &gsi::style_arrow_both, "@brief Gets the both arrow ends style code for use the \\style method\n" "When this style is specified, a two-headed arrow is drawn." ) + - gsi::method ("StyleLine|#style_line", &gsi::style_line, + gsi::method ("StyleLine", &gsi::style_line, "@brief Gets the line style code for use with the \\style method\n" "When this style is specified, a plain line is drawn." ) + - gsi::method ("StyleCrossStart|#style_cross_start", &gsi::style_cross_start, + gsi::method ("StyleCrossStart", &gsi::style_cross_start, "@brief Gets the line style code for use with the \\style method\n" "When this style is specified, a cross is drawn at the start point.\n" "\n" "This constant has been added in version 0.26." ) + - gsi::method ("StyleCrossEnd|#style_cross_end", &gsi::style_cross_end, + gsi::method ("StyleCrossEnd", &gsi::style_cross_end, "@brief Gets the line style code for use with the \\style method\n" "When this style is specified, a cross is drawn at the end point.\n" "\n" "This constant has been added in version 0.26." ) + - gsi::method ("StyleCrossBoth|#style_cross_both", &gsi::style_cross_both, + gsi::method ("StyleCrossBoth", &gsi::style_cross_both, "@brief Gets the line style code for use with the \\style method\n" "When this style is specified, a cross is drawn at both points.\n" "\n" "This constant has been added in version 0.26." ) + - gsi::method ("OutlineDiag|#outline_diag", &gsi::outline_diag, + gsi::method ("OutlineDiag", &gsi::outline_diag, "@brief Gets the diagonal output code for use with the \\outline method\n" "When this outline style is specified, a line connecting start and " "end points in the given style (ruler, arrow or plain line) is drawn." ) + - gsi::method ("OutlineXY|#outline_xy", &gsi::outline_xy, + gsi::method ("OutlineXY", &gsi::outline_xy, "@brief Gets the xy outline code for use with the \\outline method\n" "When this outline style is specified, two lines are drawn: one horizontal from left " "to right and attached to the end of that a line from the bottom to the top. The lines " "are drawn in the specified style (see \\style method)." ) + - gsi::method ("OutlineDiagXY|#outline_diag_xy", &gsi::outline_diag_xy, + gsi::method ("OutlineDiagXY", &gsi::outline_diag_xy, "@brief Gets the xy plus diagonal outline code for use with the \\outline method\n" "@brief outline_xy code used by the \\outline method\n" "When this outline style is specified, three lines are drawn: one horizontal from left " @@ -594,53 +596,65 @@ gsi::Class decl_Annotation (decl_BasicAnnotation, "lay", "Annotat "is drawn connecting the start and end points directly. The lines " "are drawn in the specified style (see \\style method)." ) + - gsi::method ("OutlineYX|#outline_yx", &gsi::outline_yx , + gsi::method ("OutlineYX", &gsi::outline_yx , "@brief Gets the yx outline code for use with the \\outline method\n" "When this outline style is specified, two lines are drawn: one vertical from bottom " "to top and attached to the end of that a line from the left to the right. The lines " "are drawn in the specified style (see \\style method)." ) + - gsi::method ("OutlineDiagYX|#outline_diag_yx", &gsi::outline_diag_yx , + gsi::method ("OutlineDiagYX", &gsi::outline_diag_yx , "@brief Gets the yx plus diagonal outline code for use with the \\outline method\n" "When this outline style is specified, three lines are drawn: one vertical from bottom " "to top and attached to the end of that a line from the left to the right. Another line " "is drawn connecting the start and end points directly. The lines " "are drawn in the specified style (see \\style method)." ) + - gsi::method ("OutlineBox|#outline_box", &gsi::outline_box, + gsi::method ("OutlineBox", &gsi::outline_box, "@brief Gets the box outline code for use with the \\outline method\n" "When this outline style is specified, a box is drawn with the corners specified by the " "start and end point. All box edges are drawn in the style specified with the \\style " "attribute." ) + - gsi::method ("OutlineEllipse|#outline_ellipse", &gsi::outline_ellipse, + gsi::method ("OutlineEllipse", &gsi::outline_ellipse, "@brief Gets the ellipse outline code for use with the \\outline method\n" "When this outline style is specified, an ellipse is drawn with the extensions specified by the " "start and end point. The contour drawn as a line.\n" "\n" "This constant has been introduced in version 0.26." ) + - gsi::method ("AngleAny|#angle_any", &gsi::angle_any, + gsi::method ("OutlineAngle", &gsi::outline_angle, + "@brief Gets the angle measurement ruler outline code for use with the \\outline method\n" + "When this outline style is specified, the ruler is drawn to indicate the angle between the first and last segment.\n" + "\n" + "This constant has been introduced in version 0.28." + ) + + gsi::method ("OutlineRadius", &gsi::outline_radius, + "@brief Gets the radius measurement ruler outline code for use with the \\outline method\n" + "When this outline style is specified, the ruler is drawn to indicate a radius defined by at least three points of the ruler.\n" + "\n" + "This constant has been introduced in version 0.28." + ) + + gsi::method ("AngleAny", &gsi::angle_any, "@brief Gets the any angle code for use with the \\angle_constraint method\n" "If this value is specified for the angle constraint, all angles will be allowed." ) + - gsi::method ("AngleDiagonal|#angle_diagonal", &gsi::angle_diagonal, + gsi::method ("AngleDiagonal", &gsi::angle_diagonal, "@brief Gets the diagonal angle code for use with the \\angle_constraint method\n" "If this value is specified for the angle constraint, only multiples of 45 degree are allowed." ) + - gsi::method ("AngleOrtho|#angle_ortho", &gsi::angle_ortho, + gsi::method ("AngleOrtho", &gsi::angle_ortho, "@brief Gets the ortho angle code for use with the \\angle_constraint method\n" "If this value is specified for the angle constraint, only multiples of 90 degree are allowed." ) + - gsi::method ("AngleHorizontal|#angle_horizontal", &gsi::angle_horizontal, + gsi::method ("AngleHorizontal", &gsi::angle_horizontal, "@brief Gets the horizontal angle code for use with the \\angle_constraint method\n" "If this value is specified for the angle constraint, only horizontal rulers are allowed." ) + - gsi::method ("AngleVertical|#angle_vertical", &gsi::angle_vertical, + gsi::method ("AngleVertical", &gsi::angle_vertical, "@brief Gets the vertical angle code for use with the \\angle_constraint method\n" "If this value is specified for the angle constraint, only vertical rulers are allowed." ) + - gsi::method ("AngleGlobal|#angle_global", &gsi::angle_global, + gsi::method ("AngleGlobal", &gsi::angle_global, "@brief Gets the global angle code for use with the \\angle_constraint method.\n" "This code will tell the ruler or marker to use the angle constraint defined globally." ) + From 996f0d75e51db4f50497832ba82f268e31e1e5d2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 1 Oct 2022 00:43:23 +0200 Subject: [PATCH 15/54] WIP: debugging of angle ruler, display options implemented --- src/ant/ant/antObject.cc | 125 +++++++++++++++++ src/ant/ant/antObject.h | 20 +++ src/ant/ant/antPlugin.cc | 4 +- src/ant/ant/antService.cc | 276 ++++++++++++++++++-------------------- 4 files changed, 276 insertions(+), 149 deletions(-) diff --git a/src/ant/ant/antObject.cc b/src/ant/ant/antObject.cc index 510ce87a3..b17a8cef3 100644 --- a/src/ant/ant/antObject.cc +++ b/src/ant/ant/antObject.cc @@ -452,6 +452,22 @@ public: out = (trans * p2 (obj)).x (); } else if (m_function == 'Q') { out = (trans * p2 (obj)).y (); + } else if (m_function == 'R') { + double r, a1, a2; + db::DPoint c; + if (obj.compute_interpolating_circle (r, c, a1, a2)) { + out = tl::Variant (r); + } else { + out = tl::Variant (); + } + } else if (m_function == 'G') { + double r, a1, a2; + db::DPoint c; + if (obj.compute_angle_parameters (r, c, a1, a2)) { + out = tl::Variant ((a2 - a1) * 180.0 / M_PI); + } else { + out = tl::Variant (); + } } else { out = tl::Variant (); } @@ -512,6 +528,8 @@ Object::formatted (const std::string &fmt, const db::DFTrans &t, size_t index) c eval.define_function ("P", new AnnotationEvalFunction('P', &eval, index)); // p2.x eval.define_function ("Q", new AnnotationEvalFunction('Q', &eval, index)); // p2.y eval.define_function ("A", new AnnotationEvalFunction('A', &eval, index)); // area mm2 + eval.define_function ("R", new AnnotationEvalFunction('R', &eval, index)); // radius (if applicable) + eval.define_function ("G", new AnnotationEvalFunction('G', &eval, index)); // angle (if applicable) return eval.interpolate (fmt); } @@ -808,6 +826,113 @@ Object::to_string () const return r; } +bool +Object::compute_interpolating_circle (double &radius, db::DPoint ¢er, double &start_angle, double &stop_angle) const +{ + if (m_points.size () < 2) { + return false; + } + + double d = m_points.back ().distance (m_points.front ()) * 0.5; + if (d < db::epsilon) { + return false; + } + + db::DVector n = m_points.back () - m_points.front (); + db::DPoint m = m_points.front () + n * 0.5; + n = db::DVector (n.y (), -n.x ()) * (1.0 / d); + + double nom = 0.0; + double div = 0.0; + + for (size_t i = 1; i + 1 < m_points.size (); ++i) { + db::DVector p = m_points [i] - m; + double pn = db::sprod (p, n); + div += pn * pn; + nom += pn * (p.sq_double_length () - d * d); + } + + if (div < db::epsilon) { + return false; + } + + double l = 0.5 * nom / div; + radius = sqrt (l * l + d * d); + center = m + n * l; + + double a = atan2 (-n.y (), -n.x ()); + double da = atan2 (d, l); + + if (fabs (l) < db::epsilon) { + + start_angle = 0.0; + stop_angle = M_PI * 2.0; + + } else if (l < 0.0) { + + start_angle = a + da; + stop_angle = start_angle + 2.0 * (M_PI - da); + + } else { + + start_angle = a - da; + stop_angle = a + da; + + } + + while (stop_angle < start_angle - db::epsilon) { + stop_angle += M_PI * 2.0; + } + + return true; +} + +bool +Object::compute_angle_parameters (double &radius, db::DPoint ¢er, double &start_angle, double &stop_angle) const +{ + if (m_points.size () < 3) { + return false; + } + + db::DPoint p1 = m_points.front (), p2 = m_points.back (); + + db::DVector pc; + for (size_t i = 1; i + 1 < m_points.size (); ++i) { + pc += m_points[i] - db::DPoint (); + } + center = db::DPoint () + pc * (1.0 / double (m_points.size () - 2)); + + db::DVector v1 (p1 - center); + if (v1.double_length () < db::epsilon) { + return false; + } + + db::DVector v2 (p2 - center); + if (v2.double_length () < db::epsilon) { + return false; + } + + radius = std::min (v1.double_length (), v2.double_length ()); + + v1 *= 1.0 / v1.double_length (); + v2 *= 1.0 / v2.double_length (); + + if (db::vprod_sign (v1, v2) == 0) { + return false; + } + + start_angle = 0.0; + stop_angle = 0.0; + start_angle = atan2 (v1.y (), v1.x ()); + stop_angle = atan2 (v2.y (), v2.x ()); + + while (stop_angle < start_angle - db::epsilon) { + stop_angle += M_PI * 2.0; + } + + return true; +} + void Object::property_changed () { diff --git a/src/ant/ant/antObject.h b/src/ant/ant/antObject.h index e225ce593..5bcb800ed 100644 --- a/src/ant/ant/antObject.h +++ b/src/ant/ant/antObject.h @@ -759,6 +759,26 @@ public: */ virtual std::string to_string () const; + /** + * @brief Computes the parameters for an angle ruler + * @param radius Returns the radius + * @param center Returns the center point + * @param start_angle Returns the start angle (in radians) + * @param stop_angle Returns the stop angle (in radians) + * @return True, if the ruler represents an angle measurement + */ + bool compute_angle_parameters (double &radius, db::DPoint ¢er, double &start_angle, double &stop_angle) const; + + /** + * @brief Computes the parameters for a radius ruler + * @param radius Returns the radius + * @param center Returns the center point + * @param start_angle Returns the start angle (in radians) + * @param stop_angle Returns the stop angle (in radians) + * @return True, if the ruler represents an angle measurement + */ + bool compute_interpolating_circle (double &radius, db::DPoint ¢er, double &start_angle, double &stop_angle) const; + protected: /** * @brief A notification method that is called when a property of the annotation has changed diff --git a/src/ant/ant/antPlugin.cc b/src/ant/ant/antPlugin.cc index 7175b11c9..f95d9ca08 100644 --- a/src/ant/ant/antPlugin.cc +++ b/src/ant/ant/antPlugin.cc @@ -77,10 +77,10 @@ static std::vector make_standard_templates () templates.push_back (ant::Template (tl::to_string (tr ("Measure")), "$X", "$Y", "$D", ant::Object::STY_ruler, ant::Object::OL_diag, true, lay::AC_Global, "_measure")); templates.back ().set_mode (ant::Template::RulerAutoMetric); - templates.push_back (ant::Template (tl::to_string (tr ("Angle")), "$X", "$Y", "$D", ant::Object::STY_line, ant::Object::OL_angle, true, lay::AC_Global, "_angle")); + templates.push_back (ant::Template (tl::to_string (tr ("Angle")), "", "", "$(sprintf('%.5g',G))°", ant::Object::STY_line, ant::Object::OL_angle, true, lay::AC_Global, "_angle")); templates.back ().set_mode (ant::Template::RulerThreeClicks); - templates.push_back (ant::Template (tl::to_string (tr ("Radius")), "$X", "$Y", "$D", ant::Object::STY_line, ant::Object::OL_radius, true, lay::AC_Global, "_radius")); + templates.push_back (ant::Template (tl::to_string (tr ("Radius")), "", "", "$R", ant::Object::STY_line, ant::Object::OL_radius, true, lay::AC_Global, "_radius")); templates.back ().set_mode (ant::Template::RulerThreeClicks); templates.push_back (ant::Template (tl::to_string (tr ("Ellipse")), "W=$(abs(X))", "H=$(abs(Y))", "", ant::Object::STY_line, ant::Object::OL_ellipse, true, lay::AC_Global, std::string ())); diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index cee1d246e..362ffe7ea 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -127,7 +127,8 @@ draw_ruler (const db::DPoint &q1, lay::CanvasPlane *bitmap, lay::Renderer &renderer, bool first_segment, - bool last_segment) + bool last_segment, + bool no_line = false) { double arrow_width = 8 / renderer.resolution (); double arrow_length = 12 / renderer.resolution (); @@ -161,12 +162,14 @@ draw_ruler (const db::DPoint &q1, // normal and unit vector double len = q1.double_distance (q2); - if ((style == ant::Object::STY_arrow_end || style == ant::Object::STY_arrow_start) && len < double (arrow_length) * 1.2) { - arrow_length = len / 1.2; - arrow_width = arrow_length * 2.0 / 3.0; - } else if (style == ant::Object::STY_arrow_both && len < double (arrow_length) * 2.4) { - arrow_length = len / 2.4; - arrow_width = arrow_length * 2.0 / 3.0; + if (! no_line && len < double (arrow_length) * 2.4) { + if ((style == ant::Object::STY_arrow_end || style == ant::Object::STY_arrow_start)) { + arrow_length = len / 1.2; + arrow_width = arrow_length * 2.0 / 3.0; + } else if (style == ant::Object::STY_arrow_both) { + arrow_length = len / 2.4; + arrow_width = arrow_length * 2.0 / 3.0; + } } db::DVector qq (q2.y () - q1.y (), q1.x () - q2.x ()); @@ -186,38 +189,46 @@ draw_ruler (const db::DPoint &q1, qu = db::DVector (1.0, 0.0); } - // produce polygon stuff + // produce line in selected and unselected mode - if (sel && style != ant::Object::STY_none) { + if (! no_line && style != ant::Object::STY_none) { + + if (sel) { + + db::DVector qw = qq * (sel_width * 0.5); + + db::DVector dq1, dq2; + if (! first_segment) { + // no start indicator if not first segment + } else if (style == ant::Object::STY_arrow_both || style == ant::Object::STY_arrow_start) { + dq1 = qu * (arrow_length - 1); + } else if (style == ant::Object::STY_cross_both || style == ant::Object::STY_cross_start) { + dq1 = qu * (sel_width * 0.5); + } + if (! last_segment) { + // no end indicator if not last segment + } else if (style == ant::Object::STY_arrow_both || style == ant::Object::STY_arrow_end) { + dq2 = qu * -(arrow_length - 1); + } else if (style == ant::Object::STY_cross_both || style == ant::Object::STY_cross_end) { + dq2 = qu * -(sel_width * 0.5); + } + + db::DPolygon p; + db::DPoint points[] = { + db::DPoint (q1 + dq1 + qw), + db::DPoint (q2 + dq2 + qw), + db::DPoint (q2 + dq2 - qw), + db::DPoint (q1 + dq1 - qw), + }; + p.assign_hull (points, points + sizeof (points) / sizeof (points [0])); + renderer.draw (p, bitmap, bitmap, 0, 0); + + } else { + + renderer.draw (db::DEdge (q1, q2), 0, bitmap, 0, 0); - db::DVector qw = qq * (sel_width * 0.5); - - db::DVector dq1, dq2; - if (! first_segment) { - // no start indicator if not first segment - } else if (style == ant::Object::STY_arrow_both || style == ant::Object::STY_arrow_start) { - dq1 = qu * (arrow_length - 1); - } else if (style == ant::Object::STY_cross_both || style == ant::Object::STY_cross_start) { - dq1 = qu * (sel_width * 0.5); - } - if (! last_segment) { - // no end indicator if not last segment - } else if (style == ant::Object::STY_arrow_both || style == ant::Object::STY_arrow_end) { - dq2 = qu * -(arrow_length - 1); - } else if (style == ant::Object::STY_cross_both || style == ant::Object::STY_cross_end) { - dq2 = qu * -(sel_width * 0.5); } - db::DPolygon p; - db::DPoint points[] = { - db::DPoint (q1 + dq1 + qw), - db::DPoint (q2 + dq2 + qw), - db::DPoint (q2 + dq2 - qw), - db::DPoint (q1 + dq1 - qw), - }; - p.assign_hull (points, points + sizeof (points) / sizeof (points [0])); - renderer.draw (p, bitmap, bitmap, 0, 0); - } if (! last_segment) { @@ -282,12 +293,6 @@ draw_ruler (const db::DPoint &q1, } - // produce edge and text stuff - - if (! sel && style != ant::Object::STY_none) { - renderer.draw (db::DEdge (q1, q2), 0, bitmap, 0, 0); - } - // create three tick vectors in tv_text, tv_short and tv_long double tf = tick_length; @@ -680,67 +685,6 @@ draw_ruler_ellipse (const ant::Object &ruler, const db::DCplxTrans &trans, bool draw_ellipse (q1, q2, lu, sel, bitmap, renderer); } -bool -compute_interpolating_circle (const ant::Object::point_list &points, double &radius, db::DPoint ¢er, double &start_angle, double &stop_angle) -{ - if (points.size () < 2) { - return false; - } - - double d = points.back ().distance (points.front ()) * 0.5; - if (d < db::epsilon) { - return false; - } - - db::DVector n = points.back () - points.front (); - db::DPoint m = points.front () + n * 0.5; - n = db::DVector (n.y (), -n.x ()) * (1.0 / d); - - double nom = 0.0; - double div = 0.0; - - for (size_t i = 1; i + 1 < points.size (); ++i) { - db::DVector p = points [i] - m; - double pn = db::sprod (p, n); - div += pn * pn; - nom += pn * (p.sq_double_length () - d * d); - } - - if (div < db::epsilon) { - return false; - } - - double l = 0.5 * nom / div; - radius = sqrt (l * l + d * d); - center = m + n * l; - - double a = atan2 (-n.y (), -n.x ()); - double da = atan2 (d, l); - - if (fabs (l) < db::epsilon) { - - start_angle = 0.0; - stop_angle = M_PI * 2.0; - - } else if (l < 0.0) { - - start_angle = a + da; - stop_angle = start_angle + 2.0 * (M_PI - da); - - } else { - - start_angle = a - da; - stop_angle = a + da; - - } - - while (stop_angle < start_angle - db::epsilon) { - stop_angle += M_PI * 2.0; - } - - return true; -} - void draw_ruler_radius (const ant::Object &ruler, const db::DCplxTrans &trans, bool sel, lay::CanvasPlane *bitmap, lay::Renderer &renderer) { @@ -755,7 +699,7 @@ draw_ruler_radius (const ant::Object &ruler, const db::DCplxTrans &trans, bool s db::DPoint center; // circle interpolation - if (compute_interpolating_circle (ruler.points (), radius, center, start_angle, stop_angle)) { + if (ruler.compute_interpolating_circle (radius, center, start_angle, stop_angle)) { // draw circle segment db::DVector rr (radius, radius); @@ -779,62 +723,100 @@ draw_ruler_radius (const ant::Object &ruler, const db::DCplxTrans &trans, bool s void draw_ruler_angle (const ant::Object &ruler, const db::DCplxTrans &trans, bool sel, lay::CanvasPlane *bitmap, lay::Renderer &renderer) { - // draw segments in diag mode - ant::Object basic = ruler; - basic.outline (ant::Object::OL_diag); - draw_ruler_segment (basic, 0, trans, sel, bitmap, renderer); - if (basic.segments () > 1) { - draw_ruler_segment (basic, basic.segments () - 1, trans, sel, bitmap, renderer); + // draw guiding segments in diag/plain line mode + + for (int p = 0; p < 2; ++p) { + + auto p1 = (p == 0 ? ruler.p1 () : ruler.seg_p1 (ruler.segments () - 1)); + auto p2 = (p == 0 ? ruler.seg_p2 (0) : ruler.p2 ()); + + auto v = lay::snap (trans * p1, trans * p2); + auto q1 = v.first; + auto q2 = v.second; + + double lu = p1.double_distance (p2); + + draw_ruler (q1, q2, lu, 0.0, sel, false, ant::Object::STY_line, bitmap, renderer, true, true); + } - if (ruler.points ().size () < 3) { + double radius = 0.0, start_angle = 0.0, stop_angle = 0.0; + db::DPoint center; + + if (! ruler.compute_angle_parameters (radius, center, start_angle, stop_angle)) { return; } - db::DPoint p1 = ruler.p1 (), p2 = ruler.p2 (); + double circle_radius = 0.9 * radius; - db::DVector pc; - for (size_t i = 1; i + 1 < ruler.points ().size (); ++i) { - pc += ruler.points ()[i] - db::DPoint (); - } - db::DPoint center = db::DPoint () + pc * (1.0 / double (ruler.points ().size () - 2)); + // draw decorations at start/end + + for (int p = 0; p < 2; ++p) { + + double a = (p == 0 ? start_angle : stop_angle); + + db::DPoint p1 = center + db::DVector (cos (a), sin (a)) * circle_radius; + + auto v = lay::snap (trans * p1, trans * p1); + db::DVector vn = db::DVector (-sin (a), cos (a)); + auto q1 = v.first + vn * (p == 0 ? 0.0 : -1.0); + auto q2 = v.second + vn * (p == 0 ? 1.0 : 0.0); + + double lu = abs (circle_radius * (stop_angle - start_angle)); + + draw_ruler (q1, q2, lu, 0.0, sel, false, ruler.style (), bitmap, renderer, p == 0, p != 0, true); - db::DVector v1 (p1 - center); - if (v1.double_length () < db::epsilon) { - return; } - db::DVector v2 (p2 - center); - if (v2.double_length () < db::epsilon) { - return; - } - - double radius = std::min (v1.double_length (), v2.double_length ()); - - v1 *= 1.0 / v1.double_length (); - v2 *= 1.0 / v2.double_length (); - - if (db::vprod_sign (v1, v2) == 0) { - return; - } - - double start_angle = 0.0, stop_angle = 0.0; - start_angle = atan2 (v1.y (), v1.x ()); - stop_angle = atan2 (v2.y (), v2.x ()); - - if (db::vprod_sign (v1, v2) < 0) { - std::swap (start_angle, stop_angle); - } - - while (stop_angle < start_angle - db::epsilon) { - stop_angle += M_PI * 2.0; - } - - db::DVector rr (radius * 0.9, radius * 0.9); + db::DVector rr (circle_radius, circle_radius); std::pair v = lay::snap (trans * (center - rr), trans * (center + rr)); draw_ellipse (v.first, v.second, radius * 2.0, sel, bitmap, renderer, start_angle, stop_angle); - // @@@ TODO: draw text, apply styles to circle segments? ... + if (ruler.style () == ant::Object::STY_ruler) { + + // draw ticks if required - minor at 5 degree, major at 10 degree + + double tick_length = 8 / renderer.resolution (); + + double da = 5.0 / 180.0 * M_PI; + unsigned int major_ticks = 2; + + double n = floor (db::epsilon + std::min (2 * M_PI, stop_angle - start_angle) / da); + unsigned int ticks = (unsigned int) std::max (1.0, n); + + for (unsigned int i = 0; i <= ticks; ++i) { + + double l = tick_length * ((i % major_ticks) == 0 ? 1.0 : 0.5); + + double a = start_angle + i * da; + db::DVector tv (cos (a), sin (a)); + db::DPoint p1 = center + tv * circle_radius; + + auto v = lay::snap (trans * p1, trans * p1); + auto q1 = v.first; + auto q2 = v.second + tv * l; + + renderer.draw (db::DEdge (q1, q2), 0, bitmap, 0, 0); + + } + + } + + { + double ta = 0.5 * (stop_angle + start_angle); + + db::DPoint tp = center + db::DVector (cos (ta), sin (ta)) * circle_radius; + db::DVector tv = db::DVector (-sin (ta), cos (ta)); + + auto v = lay::snap (trans * tp, trans * tp); + auto q1 = v.first + tv; + auto q2 = v.second - tv; + + double lu = abs (circle_radius * (stop_angle - start_angle)); + + draw_text (q1, q2, lu, ruler.text (0), false, ruler.style (), ruler.main_position (), ruler.main_xalign (), ruler.main_yalign (), bitmap, renderer); + + } } void From 1057c0f268e11001174bf01c205b7dde4e7f630f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 1 Oct 2022 01:38:18 +0200 Subject: [PATCH 16/54] WIP: debugging of radius rulers --- src/ant/ant/antObject.cc | 12 ++---------- src/ant/ant/antPlugin.cc | 3 ++- src/ant/ant/antService.cc | 9 ++++++++- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ant/ant/antObject.cc b/src/ant/ant/antObject.cc index b17a8cef3..4969bc613 100644 --- a/src/ant/ant/antObject.cc +++ b/src/ant/ant/antObject.cc @@ -452,14 +452,6 @@ public: out = (trans * p2 (obj)).x (); } else if (m_function == 'Q') { out = (trans * p2 (obj)).y (); - } else if (m_function == 'R') { - double r, a1, a2; - db::DPoint c; - if (obj.compute_interpolating_circle (r, c, a1, a2)) { - out = tl::Variant (r); - } else { - out = tl::Variant (); - } } else if (m_function == 'G') { double r, a1, a2; db::DPoint c; @@ -528,7 +520,6 @@ Object::formatted (const std::string &fmt, const db::DFTrans &t, size_t index) c eval.define_function ("P", new AnnotationEvalFunction('P', &eval, index)); // p2.x eval.define_function ("Q", new AnnotationEvalFunction('Q', &eval, index)); // p2.y eval.define_function ("A", new AnnotationEvalFunction('A', &eval, index)); // area mm2 - eval.define_function ("R", new AnnotationEvalFunction('R', &eval, index)); // radius (if applicable) eval.define_function ("G", new AnnotationEvalFunction('G', &eval, index)); // angle (if applicable) return eval.interpolate (fmt); } @@ -840,7 +831,7 @@ Object::compute_interpolating_circle (double &radius, db::DPoint ¢er, double db::DVector n = m_points.back () - m_points.front (); db::DPoint m = m_points.front () + n * 0.5; - n = db::DVector (n.y (), -n.x ()) * (1.0 / d); + n = db::DVector (n.y (), -n.x ()) * (0.5 / d); double nom = 0.0; double div = 0.0; @@ -872,6 +863,7 @@ Object::compute_interpolating_circle (double &radius, db::DPoint ¢er, double start_angle = a + da; stop_angle = start_angle + 2.0 * (M_PI - da); + std::swap (start_angle, stop_angle); // @@@ } else { diff --git a/src/ant/ant/antPlugin.cc b/src/ant/ant/antPlugin.cc index f95d9ca08..3545deb40 100644 --- a/src/ant/ant/antPlugin.cc +++ b/src/ant/ant/antPlugin.cc @@ -80,8 +80,9 @@ static std::vector make_standard_templates () templates.push_back (ant::Template (tl::to_string (tr ("Angle")), "", "", "$(sprintf('%.5g',G))°", ant::Object::STY_line, ant::Object::OL_angle, true, lay::AC_Global, "_angle")); templates.back ().set_mode (ant::Template::RulerThreeClicks); - templates.push_back (ant::Template (tl::to_string (tr ("Radius")), "", "", "$R", ant::Object::STY_line, ant::Object::OL_radius, true, lay::AC_Global, "_radius")); + templates.push_back (ant::Template (tl::to_string (tr ("Radius")), "", "", "R=$D", ant::Object::STY_arrow_end, ant::Object::OL_radius, true, lay::AC_Global, "_radius")); templates.back ().set_mode (ant::Template::RulerThreeClicks); + templates.back ().set_main_position (ant::Object::POS_center); templates.push_back (ant::Template (tl::to_string (tr ("Ellipse")), "W=$(abs(X))", "H=$(abs(Y))", "", ant::Object::STY_line, ant::Object::OL_ellipse, true, lay::AC_Global, std::string ())); diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index 362ffe7ea..41be56346 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -709,12 +709,19 @@ draw_ruler_radius (const ant::Object &ruler, const db::DCplxTrans &trans, bool s double a = 0.5 * (start_angle + stop_angle); db::DPoint rc = center + db::DVector (cos (a), sin (a)) * radius; +#if 0 // draw a center marker ant::Object center_loc (center, center, 0, std::string (), ruler.fmt_x (), ruler.fmt_y (), ant::Object::STY_cross_start, ant::Object::OL_diag, false, lay::AC_Global); draw_ruler_segment (center_loc, 0, trans, sel, bitmap, renderer); +#endif // draw the radius ruler - ant::Object radius (center, rc, 0, ruler.fmt (), std::string (), std::string (), ruler.style (), ruler.outline (), false, lay::AC_Global); + ant::Object radius = ruler; + radius.outline (ant::Object::OL_diag); + ant::Object::point_list pts; + pts.push_back (center); + pts.push_back (rc); + radius.set_points (pts); draw_ruler_segment (radius, 0, trans, sel, bitmap, renderer); } From 1660a72b5a158679546a3e6c40fe6f3165c39f48 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 1 Oct 2022 01:45:47 +0200 Subject: [PATCH 17/54] WIP: restored logo on 'About' page --- src/lay/lay/HelpAboutDialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lay/lay/HelpAboutDialog.ui b/src/lay/lay/HelpAboutDialog.ui index 69753802a..07b329094 100644 --- a/src/lay/lay/HelpAboutDialog.ui +++ b/src/lay/lay/HelpAboutDialog.ui @@ -137,7 +137,7 @@ - :/logo@2x.png + :/logo.png From 069c3e64474f1abafc3cd52471ff3acba29b1fcc Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 1 Oct 2022 12:39:05 +0200 Subject: [PATCH 18/54] WIP: proper selection of angle and radius rulers --- src/ant/ant/antObject.cc | 9 ++++-- src/ant/ant/antService.cc | 58 +++++++++++++++++++++++++++++---------- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/src/ant/ant/antObject.cc b/src/ant/ant/antObject.cc index 4969bc613..41d750642 100644 --- a/src/ant/ant/antObject.cc +++ b/src/ant/ant/antObject.cc @@ -861,9 +861,8 @@ Object::compute_interpolating_circle (double &radius, db::DPoint ¢er, double } else if (l < 0.0) { - start_angle = a + da; - stop_angle = start_angle + 2.0 * (M_PI - da); - std::swap (start_angle, stop_angle); // @@@ + stop_angle = a + da; + start_angle = stop_angle + 2.0 * (M_PI - da); } else { @@ -918,6 +917,10 @@ Object::compute_angle_parameters (double &radius, db::DPoint ¢er, double &st start_angle = atan2 (v1.y (), v1.x ()); stop_angle = atan2 (v2.y (), v2.x ()); + if (db::vprod_sign (v1, v2) < 0) { + std::swap (stop_angle, start_angle); + } + while (stop_angle < start_angle - db::epsilon) { stop_angle += M_PI * 2.0; } diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index 41be56346..cb48588f9 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -44,6 +44,8 @@ namespace ant { +double angle_ruler_radius_factor = 0.9; + // ------------------------------------------------------------- // Convert buttons to an angle constraint @@ -544,17 +546,10 @@ draw_ellipse (const db::DPoint &q1, int npoints = int (floor (200 * abs (stop_angle - start_angle) / (2.0 * M_PI))); - // produce polygon stuff - double rx = fabs ((q2 - q1).x () * 0.5); double ry = fabs ((q2 - q1).y () * 0.5); db::DPoint c = q1 + (q2 - q1) * 0.5; - if (sel) { - rx += sel_width * 0.5; - ry += sel_width * 0.5; - } - std::vector pts; pts.reserve (npoints + 1); @@ -754,7 +749,7 @@ draw_ruler_angle (const ant::Object &ruler, const db::DCplxTrans &trans, bool se return; } - double circle_radius = 0.9 * radius; + double circle_radius = angle_ruler_radius_factor * radius; // draw decorations at start/end @@ -845,10 +840,38 @@ draw_ruler (const ant::Object &ruler, const db::DCplxTrans &trans, bool sel, lay } } +static bool +is_selected_by_circle_segment (const ant::Object &ruler, const db::DPoint &pos, double enl, double &distance) +{ + double r = 0.0, a1 = 0.0, a2 = 0.0; + db::DPoint c; + + bool good; + if (ruler.outline () == ant::Object::OL_angle) { + good = ruler.compute_angle_parameters (r, c, a1, a2); + r *= angle_ruler_radius_factor; + } else { + good = ruler.compute_interpolating_circle (r, c, a1, a2); + } + if (good && fabs (pos.distance (c) - r) < enl) { + + double a = atan2 ((pos - c).y (), (pos - c).x ()) - 2 * M_PI; + while (a < a1 - db::epsilon) { + a += 2 * M_PI; + } + if (a < a2 + db::epsilon) { + distance = std::min (distance, fabs (pos.distance (c) - r)); + return true; + } + + } + + return false; +} + static bool is_selected (const ant::Object &ruler, size_t index, const db::DPoint &pos, double enl, double &distance) { -// @@@ angle, radius ant::Object::outline_type outline = ruler.outline (); db::DPoint p1 = ruler.seg_p1 (index), p2 = ruler.seg_p2 (index); @@ -868,7 +891,7 @@ is_selected (const ant::Object &ruler, size_t index, const db::DPoint &pos, doub db::DPoint ref = b.center () + db::DVector (dx * b.width () * 0.5 / dd, dy * b.height () * 0.5 / dd); double d = ref.distance (pos); if (d < enl) { - distance = d; + distance = std::min (distance, d); return true; } } @@ -890,6 +913,8 @@ is_selected (const ant::Object &ruler, size_t index, const db::DPoint &pos, doub unsigned int nedges = 0; if (outline == ant::Object::OL_diag || + outline == ant::Object::OL_angle || + outline == ant::Object::OL_radius || outline == ant::Object::OL_diag_xy || outline == ant::Object::OL_diag_yx) { edges [nedges++] = db::DEdge (p1, p2); @@ -912,7 +937,7 @@ is_selected (const ant::Object &ruler, size_t index, const db::DPoint &pos, doub for (unsigned int i = 0; i < nedges; ++i) { double d = edges [i].distance_abs (pos); if (d <= enl) { - distance = d; + distance = std::min (distance, d); return true; } } @@ -923,11 +948,15 @@ is_selected (const ant::Object &ruler, size_t index, const db::DPoint &pos, doub static bool is_selected (const ant::Object &ruler, const db::DPoint &pos, double enl, double &distance) { - if (ruler.outline () == ant::Object::OL_box || ruler.outline () == ant::Object::OL_ellipse || ruler.outline () == ant::Object::OL_angle || ruler.outline () == ant::Object::OL_radius) { + distance = std::numeric_limits::max (); + bool any = false; + + if (ruler.outline () == ant::Object::OL_box || ruler.outline () == ant::Object::OL_ellipse) { return is_selected (ruler, std::numeric_limits::max (), pos, enl, distance); + } else if (ruler.outline () == ant::Object::OL_angle || ruler.outline () == ant::Object::OL_radius) { + any = is_selected_by_circle_segment (ruler, pos, enl, distance); } - bool any = false; for (size_t index = 0; index < ruler.segments (); ++index) { // NOTE: we check *all* since distance is updated herein. if (is_selected (ruler, index, pos, enl, distance)) { @@ -1242,7 +1271,6 @@ Service::insert_ruler (const ant::Object &ruler, bool limit_number) static bool dragging_what_seg (const ant::Object *robj, const db::DBox &search_dbox, ant::Service::MoveMode &mode, db::DPoint &p1, size_t index) { -// @@@ radius, angle ant::Object::outline_type outline = robj->outline (); db::DPoint p12, p21; @@ -1324,7 +1352,7 @@ dragging_what (const ant::Object *robj, const db::DBox &search_dbox, ant::Servic { ant::Object::outline_type outline = robj->outline (); - if (outline == ant::Object::OL_box || outline == ant::Object::OL_ellipse || outline == ant::Object::OL_angle || outline == ant::Object::OL_radius) { + if (outline == ant::Object::OL_box || outline == ant::Object::OL_ellipse) { index = std::numeric_limits::max (); return dragging_what_seg (robj, search_dbox, mode, p1, index); } From eedfc7c00f4764014e7a42d5932832b4a94dd9a8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 1 Oct 2022 13:04:25 +0200 Subject: [PATCH 19/54] Doc update, tests for new annotation features --- src/doc/doc/manual/ruler_properties.xml | 1 + testdata/ruby/antTest.rb | 233 +++++++++++++++++------- 2 files changed, 170 insertions(+), 64 deletions(-) diff --git a/src/doc/doc/manual/ruler_properties.xml b/src/doc/doc/manual/ruler_properties.xml index d889bd883..a1ffed576 100644 --- a/src/doc/doc/manual/ruler_properties.xml +++ b/src/doc/doc/manual/ruler_properties.xml @@ -76,6 +76,7 @@
  • X: The horizontal extension of the ruler in micron units.
  • Y: The vertical extension of the ruler in micron units.
  • A: The area enclosed by the ruler (if it was a box) in square millimeters.
  • +
  • G: The angle enclosed by the first and last segment of the ruler (used for angle measurement rulers).
  • diff --git a/testdata/ruby/antTest.rb b/testdata/ruby/antTest.rb index 357cebe98..34677cd68 100644 --- a/testdata/ruby/antTest.rb +++ b/testdata/ruby/antTest.rb @@ -37,6 +37,19 @@ def annot_obj( p1, p2, fmt, fmt_x, fmt_y, style, outline, snap, ac ) return a end +def annot_obj2( pts, fmt, fmt_x, fmt_y, style, outline, snap, ac ) + a = RBA::Annotation::new + a.points = pts + a.fmt = fmt + a.fmt_x = fmt_x + a.fmt_y = fmt_y + a.style = style + a.outline = outline + a.snap = snap + a.angle_constraint = ac + return a +end + class RBA::Annotation def style_to_s(s) if s == StyleRuler @@ -72,6 +85,12 @@ class RBA::Annotation "diag_yx" elsif s == OutlineBox "diag_box" + elsif s == OutlineEllipse + "ellipse" + elsif s == OutlineAngle + "angle" + elsif s == OutlineRadius + "radius" else "" end @@ -119,7 +138,7 @@ class RBA::Annotation "" end end - def to_s + def to_s_debug s = "p1=" + p1.to_s + ", p2=" + p2.to_s + ", fmt=" + fmt + ", fmt_x=" + fmt_x + ", fmt_y=" + fmt_y + ", style=" + self.style_to_s(style) + ", outline=" + self.outline_to_s(outline) + ", snap=" + snap?.to_s + ", ac=" + self.ac_to_s(angle_constraint) if category != "" @@ -160,13 +179,52 @@ class Ant_TestClass < TestBase end a = RBA::Annotation::new - assert_equal( a.to_s, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=diag, snap=true, ac=global" ) + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=diag, snap=true, ac=global" ) + + a.outline = RBA::Annotation::OutlineXY + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=xy, snap=true, ac=global" ) + a.outline = RBA::Annotation::OutlineDiagXY + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=diag_xy, snap=true, ac=global" ) + a.outline = RBA::Annotation::OutlineYX + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=yx, snap=true, ac=global" ) + a.outline = RBA::Annotation::OutlineDiagYX + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=diag_yx, snap=true, ac=global" ) + a.outline = RBA::Annotation::OutlineBox + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=diag_box, snap=true, ac=global" ) + a.outline = RBA::Annotation::OutlineEllipse + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=ellipse, snap=true, ac=global" ) + a.outline = RBA::Annotation::OutlineAngle + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=angle, snap=true, ac=global" ) + a.outline = RBA::Annotation::OutlineRadius + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=radius, snap=true, ac=global" ) + a.outline = RBA::Annotation::OutlineDiag + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=diag, snap=true, ac=global" ) + + a.style = RBA::Annotation::StyleArrowEnd + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=arrow_end, outline=diag, snap=true, ac=global" ) + a.style = RBA::Annotation::StyleArrowStart + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=arrow_start, outline=diag, snap=true, ac=global" ) + a.style = RBA::Annotation::StyleArrowBoth + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=arrow_both, outline=diag, snap=true, ac=global" ) + a.style = RBA::Annotation::StyleLine + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=line, outline=diag, snap=true, ac=global" ) + a.style = RBA::Annotation::StyleCrossEnd + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=cross_end, outline=diag, snap=true, ac=global" ) + a.style = RBA::Annotation::StyleCrossStart + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=cross_start, outline=diag, snap=true, ac=global" ) + a.style = RBA::Annotation::StyleCrossBoth + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=cross_both, outline=diag, snap=true, ac=global" ) + a.style = RBA::Annotation::StyleRuler + assert_equal( a.to_s_debug, "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=diag, snap=true, ac=global" ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + + a = annot_obj2( [ RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ) ], "a", "b", "c", 1, 2, false, 3 ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) b = a.dup - assert_equal( b.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( b.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) a = RBA::Annotation::new assert_equal( a == b, false ) @@ -177,10 +235,10 @@ class Ant_TestClass < TestBase assert_equal( a != b, false ) c = a.transformed( RBA::DTrans::new( RBA::DPoint::new( 10.0, 20.0 ) ) ) - assert_equal( c.to_s, "p1=11,22, p2=13,24, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( c.to_s_debug, "p1=11,22, p2=13,24, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) c = a.transformed_cplx( RBA::DCplxTrans::new( RBA::DTrans::new( RBA::DPoint::new( 11.0, 22.0 ) ) ) ) - assert_equal( c.to_s, "p1=12,24, p2=14,26, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( c.to_s_debug, "p1=12,24, p2=14,26, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) a.fmt = "X=$X,Y=$Y,D=$D,A=$A,P=$P,Q=$Q,U=$U,V=$V" a.fmt_x = "$(X*1000)+$(Y*1e3)" @@ -189,199 +247,199 @@ class Ant_TestClass < TestBase assert_equal( a.text_y, "c" ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.category = "abc" - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, category=abc" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, category=abc" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.main_position = RBA::Annotation::PositionP1 - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_position=p1" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_position=p1" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.main_position = RBA::Annotation::PositionP2 - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_position=p2" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_position=p2" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.main_position = RBA::Annotation::PositionCenter - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_position=center" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_position=center" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.main_xalign = RBA::Annotation::AlignCenter - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_xalign=center" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_xalign=center" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.main_xalign = RBA::Annotation::AlignLeft - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_xalign=down" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_xalign=down" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.main_xalign = RBA::Annotation::AlignRight - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_xalign=up" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_xalign=up" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.main_yalign = RBA::Annotation::AlignCenter - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_yalign=center" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_yalign=center" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.main_yalign = RBA::Annotation::AlignLeft - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_yalign=down" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_yalign=down" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.main_yalign = RBA::Annotation::AlignRight - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_yalign=up" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, main_yalign=up" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.xlabel_xalign = RBA::Annotation::AlignCenter - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, xlabel_xalign=center" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, xlabel_xalign=center" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.xlabel_xalign = RBA::Annotation::AlignLeft - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, xlabel_xalign=down" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, xlabel_xalign=down" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.xlabel_xalign = RBA::Annotation::AlignRight - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, xlabel_xalign=up" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, xlabel_xalign=up" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.xlabel_yalign = RBA::Annotation::AlignCenter - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, xlabel_yalign=center" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, xlabel_yalign=center" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.xlabel_yalign = RBA::Annotation::AlignLeft - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, xlabel_yalign=down" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, xlabel_yalign=down" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.xlabel_yalign = RBA::Annotation::AlignRight - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, xlabel_yalign=up" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, xlabel_yalign=up" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.ylabel_xalign = RBA::Annotation::AlignCenter - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, ylabel_xalign=center" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, ylabel_xalign=center" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.ylabel_xalign = RBA::Annotation::AlignLeft - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, ylabel_xalign=down" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, ylabel_xalign=down" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.ylabel_xalign = RBA::Annotation::AlignRight - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, ylabel_xalign=up" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, ylabel_xalign=up" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.ylabel_yalign = RBA::Annotation::AlignCenter - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, ylabel_yalign=center" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, ylabel_yalign=center" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.ylabel_yalign = RBA::Annotation::AlignLeft - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, ylabel_yalign=down" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, ylabel_yalign=down" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) a = annot_obj( RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ), "a", "b", "c", 1, 2, false, 3 ) - assert_equal( a.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) aa = a.dup aa.ylabel_yalign = RBA::Annotation::AlignRight - assert_equal( aa.to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, ylabel_yalign=up" ) + assert_equal( aa.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal, ylabel_yalign=up" ) assert_equal( a == aa, false ) a = aa.dup assert_equal( a == aa, true ) @@ -421,10 +479,10 @@ class Ant_TestClass < TestBase assert_equal( a.is_valid?, true ) assert_equal( lv.annotation( -1 ).is_valid?, false ) assert_equal( lv.annotation( a.id ).is_valid?, true ) - assert_equal( lv.annotation( a.id ).to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) + assert_equal( lv.annotation( a.id ).to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) arr = [] - lv.each_annotation { |a| arr.push( a.to_s ) } + lv.each_annotation { |a| arr.push( a.to_s_debug ) } assert_equal( arr.join( ";" ), "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=diag, snap=true, ac=global;p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) id = nil @@ -445,19 +503,19 @@ class Ant_TestClass < TestBase assert_equal( asc, 1 ) arr = [] - lv.each_annotation { |a| arr.push( a.to_s ) } + lv.each_annotation { |a| arr.push( a.to_s_debug ) } assert_equal( arr.join( ";" ), "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=diag, snap=true, ac=global;p1=1,2, p2=3,4, fmt=u, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) lv.erase_annotation(id) arr = [] - lv.each_annotation { |a| arr.push( a.to_s ) } + lv.each_annotation { |a| arr.push( a.to_s_debug ) } assert_equal( arr.join( ";" ), "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=diag, snap=true, ac=global" ) lv.clear_annotations arr = [] - lv.each_annotation { |a| arr.push( a.to_s ) } + lv.each_annotation { |a| arr.push( a.to_s_debug ) } assert_equal( arr.join( ";" ), "" ) mw.close_all @@ -483,25 +541,25 @@ class Ant_TestClass < TestBase lv.insert_annotation( a ) arr = [] - lv.each_annotation { |x| arr.push( x.to_s ) } + lv.each_annotation { |x| arr.push( x.to_s_debug ) } assert_equal( arr.join( ";" ), "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=diag, snap=true, ac=global;p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) a.fmt = "u" arr = [] - lv.each_annotation { |a| arr.push( a.to_s ) } + lv.each_annotation { |a| arr.push( a.to_s_debug ) } assert_equal( arr.join( ";" ), "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=diag, snap=true, ac=global;p1=1,2, p2=3,4, fmt=u, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) a.delete arr = [] - lv.each_annotation { |a| arr.push( a.to_s ) } + lv.each_annotation { |a| arr.push( a.to_s_debug ) } assert_equal( arr.join( ";" ), "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=diag, snap=true, ac=global" ) a0.delete arr = [] - lv.each_annotation { |a| arr.push( a.to_s ) } + lv.each_annotation { |a| arr.push( a.to_s_debug ) } assert_equal( arr.join( ";" ), "" ) a0 = RBA::Annotation::new @@ -511,19 +569,19 @@ class Ant_TestClass < TestBase lv.insert_annotation( a ) arr = [] - lv.each_annotation { |x| arr.push( x.to_s ) } + lv.each_annotation { |x| arr.push( x.to_s_debug ) } assert_equal( arr.join( ";" ), "p1=0,0, p2=0,0, fmt=$D, fmt_x=$X, fmt_y=$Y, style=ruler, outline=diag, snap=true, ac=global;p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) lv.each_annotation { |x| x.fmt = "q" } arr = [] - lv.each_annotation { |x| arr.push( x.to_s ) } + lv.each_annotation { |x| arr.push( x.to_s_debug ) } assert_equal( arr.join( ";" ), "p1=0,0, p2=0,0, fmt=q, fmt_x=$X, fmt_y=$Y, style=ruler, outline=diag, snap=true, ac=global;p1=1,2, p2=3,4, fmt=q, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal" ) lv.each_annotation { |x| x.delete } arr = [] - lv.each_annotation { |a| arr.push( a.to_s ) } + lv.each_annotation { |a| arr.push( a.to_s_debug ) } assert_equal( arr.join( ";" ), "" ) mw.close_all @@ -551,14 +609,14 @@ class Ant_TestClass < TestBase arr = [] lv.each_annotation_selected { |a| arr.push(a) } assert_equal(arr.size, 1) - assert_equal(arr[0].to_s, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal") + assert_equal(arr[0].to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal") arr[0].p1 = RBA::DPoint::new(11, 12) arr = [] lv.each_annotation_selected { |a| arr.push(a) } assert_equal(arr.size, 1) - assert_equal(arr[0].to_s, "p1=11,12, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal") + assert_equal(arr[0].to_s_debug, "p1=11,12, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal") mw.close_all @@ -601,6 +659,53 @@ class Ant_TestClass < TestBase end + # multi-segment rulers + def test_6 + + a = annot_obj2( [ RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 3.0, 4.0 ) ], "a", "b", "c", 1, 2, false, 3) + assert_equal(a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal") + assert_equal(a.to_s, "id=-1,x1=1,y1=2,x2=3,y2=4,category='',fmt=a,fmt_x=b,fmt_y=c,position=auto,xalign=auto,yalign=auto,xlabel_xalign=auto,xlabel_yalign=auto,ylabel_xalign=auto,ylabel_yalign=auto,style=arrow_end,outline=diag_xy,snap=false,angle_constraint=horizontal") + aa = RBA::Annotation::from_s(a.to_s) + assert_equal(aa.to_s, a. to_s) + + a = annot_obj2( [ RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 1.5, 2.5 ), RBA::DPoint::new( 3.0, 4.0 ) ], "a", "b", "c", 1, 2, false, 3) + assert_equal(a.to_s_debug, "p1=1,2, p2=3,4, fmt=a, fmt_x=b, fmt_y=c, style=arrow_end, outline=diag_xy, snap=false, ac=horizontal") + assert_equal(a.to_s, "id=-1,pt=1:2,pt=1.5:2.5,pt=3:4,category='',fmt=a,fmt_x=b,fmt_y=c,position=auto,xalign=auto,yalign=auto,xlabel_xalign=auto,xlabel_yalign=auto,ylabel_xalign=auto,ylabel_yalign=auto,style=arrow_end,outline=diag_xy,snap=false,angle_constraint=horizontal") + assert_equal(a.segments, 2) + assert_equal(a.seg_p1(0).to_s, "1,2") + assert_equal(a.seg_p2(0).to_s, "1.5,2.5") + assert_equal(a.seg_p1(1).to_s, "1.5,2.5") + assert_equal(a.seg_p2(1).to_s, "3,4") + a.fmt_x = "$X" + a.fmt_y = "$(Y*2)" + a.fmt = "$D" + assert_equal(a.text_x(0), "0.5") + assert_equal(a.text_x(1), "1.5") + assert_equal(a.text_y(0), "1") + assert_equal(a.text_y(1), "3") + assert_equal(a.text(0), "0.70710678") + assert_equal(a.text(1), "2.1213203") + aa = RBA::Annotation::from_s(a.to_s) + assert_equal(aa.to_s, a. to_s) + + assert_equal(a.points.collect { |p| p.to_s }.join(";"), "1,2;1.5,2.5;3,4") + + a.p1 = RBA::DPoint::new(1.1, 2.1) + assert_equal(a.points.collect { |p| p.to_s }.join(";"), "1.1,2.1;1.5,2.5;3,4") + + a.p2 = RBA::DPoint::new(3.1, 4.1) + assert_equal(a.points.collect { |p| p.to_s }.join(";"), "1.1,2.1;1.5,2.5;3.1,4.1") + + a.points = [ RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 2.0, 3.0 ) ] + assert_equal(a.points.collect { |p| p.to_s }.join(";"), "1,2;2,3") + assert_equal(a.segments, 1) + + a.points = [ RBA::DPoint::new( 1.0, 2.0 ), RBA::DPoint::new( 1.0, 2.0 ) ] + assert_equal(a.points.collect { |p| p.to_s }.join(";"), "1,2") + assert_equal(a.segments, 1) + + end + end load("test_epilogue.rb") From 4e4f316b71715010dc427caf239ecd8765a49ec4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 1 Oct 2022 13:33:53 +0200 Subject: [PATCH 20/54] Updated doc. --- src/doc/doc/manual/measure.xml | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/doc/doc/manual/measure.xml b/src/doc/doc/manual/measure.xml index 836557ac3..06df213fc 100644 --- a/src/doc/doc/manual/measure.xml +++ b/src/doc/doc/manual/measure.xml @@ -5,6 +5,7 @@ Doing Measurements +

    A measurement can be performed by clicking on the ruler icon in the @@ -23,6 +24,32 @@ is attached perpendicular to the edge next to the initial point.

    +

    + You can mark a position with a single click by selecting the "Cross" + ruler type. Clicking at a location will place such a ruler. The ruler + shows the x and y coordinate. +

    + +

    + The "Multi-Ruler" allows concatenating multiple rulers into a single + object. Click and the first point to start such a ruler. Then click + on more points to add new segments to the ruler. Each segment is shown + as an individual ruler with tick marks and a length. Finish the sequence + with a double-click. +

    + +

    + The "Angle" ruler allows angle measurements. Three clicks are required + to define a "V" like arrangement of two rulers. The angle enclosed by the two lines forming the "V" + is shown in the ruler. +

    + +

    + Another special ruler, the "Radius" ruler is also a three-click type. + Specify three points to define a circle. The ruler shows the radius + of this circle and the circle outline. +

    +

    Rulers can be configured in manifold ways. Use "Rulers And Annotations Setup" in the "Edit" menu to open the ruler configuration dialog. @@ -52,7 +79,7 @@

    Rulers can be moved by selecting "Move" mode with the speedbar buttons - in the toolbar or "Move" from the "Mode" submenu in the "Edit" menu. Then + in the toolbar or "Move" from the "Mode" sub-menu in the "Edit" menu. Then left-click and drag the ruler or the ruler end point that should be changed.

    From 00e78eb76c9a0085015a6c86b9db9068c927681a Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 1 Oct 2022 16:24:05 +0200 Subject: [PATCH 21/54] Highres mode config option - display utilizes full pixel density --- src/ant/ant/antService.cc | 17 ++++---- src/laybasic/laybasic/layLayoutCanvas.cc | 41 +++++++++++++++----- src/laybasic/laybasic/layLayoutCanvas.h | 9 ++++- src/laybasic/laybasic/layLayoutViewBase.cc | 7 ++++ src/laybasic/laybasic/layLayoutViewConfig.cc | 1 + src/laybasic/laybasic/layViewObject.h | 2 +- src/laybasic/laybasic/laybasicConfig.h | 1 + src/layui/layui/LayoutViewConfigPage7.ui | 20 ++++++++-- src/layui/layui/layLayoutViewConfigPages.cc | 5 +++ 9 files changed, 81 insertions(+), 22 deletions(-) diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index cb48588f9..49c3bca93 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -32,6 +32,7 @@ #include "laybasicConfig.h" #include "layConverters.h" #include "layLayoutCanvas.h" +#include "layFixedFont.h" #if defined(HAVE_QT) # include "layProperties.h" #endif @@ -45,6 +46,8 @@ namespace ant { double angle_ruler_radius_factor = 0.9; +double ruler_tick_length = 8.0; +double ruler_arrow_width = 8.0; // ------------------------------------------------------------- // Convert buttons to an angle constraint @@ -132,8 +135,8 @@ draw_ruler (const db::DPoint &q1, bool last_segment, bool no_line = false) { - double arrow_width = 8 / renderer.resolution (); - double arrow_length = 12 / renderer.resolution (); + double arrow_width = ruler_arrow_width / renderer.resolution (); + double arrow_length = 1.5 * arrow_width; double sel_width = 2 / renderer.resolution (); if (length_u < 1e-5 /*micron*/ && style != ant::Object::STY_cross_both && style != ant::Object::STY_cross_end && style != ant::Object::STY_cross_start) { @@ -152,7 +155,7 @@ draw_ruler (const db::DPoint &q1, } else { // compute the tick distribution - double tick_length = (style == ant::Object::STY_ruler ? 8 : 0) / renderer.resolution (); + double tick_length = (style == ant::Object::STY_ruler ? ruler_tick_length : 0) / renderer.resolution (); double ticks = -1.0; int minor_ticks = -1; @@ -363,8 +366,8 @@ draw_text (const db::DPoint &q1, return; } - double arrow_width = 8 / renderer.resolution (); - double arrow_length = 12 / renderer.resolution (); + double arrow_width = ruler_arrow_width / renderer.resolution (); + double arrow_length = 1.5 * arrow_width; // Currently, "auto" means p2. if (pos == ant::Object::POS_auto) { @@ -383,7 +386,7 @@ draw_text (const db::DPoint &q1, } else { // compute the tick distribution - double tick_length = (style == ant::Object::STY_ruler ? 8 : 0) / renderer.resolution (); + double tick_length = (style == ant::Object::STY_ruler ? ruler_tick_length : 0) / renderer.resolution (); // normal and unit vector @@ -778,7 +781,7 @@ draw_ruler_angle (const ant::Object &ruler, const db::DCplxTrans &trans, bool se // draw ticks if required - minor at 5 degree, major at 10 degree - double tick_length = 8 / renderer.resolution (); + double tick_length = ruler_tick_length / renderer.resolution (); double da = 5.0 / 180.0 * M_PI; unsigned int major_ticks = 2; diff --git a/src/laybasic/laybasic/layLayoutCanvas.cc b/src/laybasic/laybasic/layLayoutCanvas.cc index 0a65906bf..ad89580fa 100644 --- a/src/laybasic/laybasic/layLayoutCanvas.cc +++ b/src/laybasic/laybasic/layLayoutCanvas.cc @@ -281,6 +281,7 @@ LayoutCanvas::LayoutCanvas (lay::LayoutViewBase *view) mp_image_fg (0), m_background (0), m_foreground (0), m_active (0), m_oversampling (1), + m_hrm (false), m_need_redraw (false), m_redraw_clearing (false), m_redraw_force_update (true), @@ -330,18 +331,28 @@ LayoutCanvas::~LayoutCanvas () #if defined(HAVE_QT) && QT_VERSION >= 0x050000 double -LayoutCanvas::dpr () +LayoutCanvas::dpr () const { return widget () ? widget ()->devicePixelRatio () : 1.0; } #else double -LayoutCanvas::dpr () +LayoutCanvas::dpr () const { return 1.0; } #endif +double +LayoutCanvas::resolution () const +{ + if (m_hrm) { + return 1.0 / m_oversampling; + } else { + return 1.0 / (m_oversampling * dpr ()); + } +} + #if defined(HAVE_QT) void LayoutCanvas::init_ui (QWidget *parent) @@ -407,6 +418,16 @@ LayoutCanvas::set_oversampling (unsigned int os) } } +void +LayoutCanvas::set_highres_mode (bool hrm) +{ + if (hrm != m_hrm) { + m_image_cache.clear (); + m_hrm = hrm; + do_redraw_all (); + } +} + void LayoutCanvas::set_colors (tl::Color background, tl::Color foreground, tl::Color active) { @@ -477,7 +498,7 @@ LayoutCanvas::prepare_drawing () { if (m_need_redraw) { - BitmapViewObjectCanvas::set_size (m_viewport_l.width (), m_viewport_l.height (), 1.0 / double (m_oversampling * dpr ())); + BitmapViewObjectCanvas::set_size (m_viewport_l.width (), m_viewport_l.height (), resolution ()); if (! mp_image || (unsigned int) mp_image->width () != m_viewport_l.width () || @@ -513,7 +534,7 @@ LayoutCanvas::prepare_drawing () ++c; } - mp_redraw_thread->commit (m_layers, m_viewport_l, 1.0 / double (m_oversampling * dpr ())); + mp_redraw_thread->commit (m_layers, m_viewport_l, resolution ()); if (tl::verbosity () >= 20) { tl::info << "Restored image from cache"; @@ -563,7 +584,7 @@ LayoutCanvas::prepare_drawing () } if (m_redraw_clearing) { - mp_redraw_thread->start (mp_view->synchronous () ? 0 : mp_view->drawing_workers (), m_layers, m_viewport_l, 1.0 / double (m_oversampling * dpr ()), m_redraw_force_update); + mp_redraw_thread->start (mp_view->synchronous () ? 0 : mp_view->drawing_workers (), m_layers, m_viewport_l, resolution (), m_redraw_force_update); } else { mp_redraw_thread->restart (m_need_redraw_layer); } @@ -634,7 +655,7 @@ LayoutCanvas::paint_event () } // render the main bitmaps - to_image (scaled_view_ops (m_oversampling * dpr ()), dither_pattern (), line_styles (), m_oversampling * dpr (), background_color (), foreground_color (), active_color (), this, *mp_image, m_viewport_l.width (), m_viewport_l.height ()); + to_image (scaled_view_ops (1.0 / resolution ()), dither_pattern (), line_styles (), 1.0 / resolution (), background_color (), foreground_color (), active_color (), this, *mp_image, m_viewport_l.width (), m_viewport_l.height ()); if (mp_image_fg) { delete mp_image_fg; @@ -664,7 +685,7 @@ LayoutCanvas::paint_event () if (fg_bitmaps () > 0) { tl::PixelBuffer full_image (*mp_image); - bitmaps_to_image (fg_view_op_vector (), fg_bitmap_vector (), dither_pattern (), line_styles (), m_oversampling * dpr (), &full_image, m_viewport_l.width (), m_viewport_l.height (), false, &m_mutex); + bitmaps_to_image (fg_view_op_vector (), fg_bitmap_vector (), dither_pattern (), line_styles (), 1.0 / resolution (), &full_image, m_viewport_l.width (), m_viewport_l.height (), false, &m_mutex); // render the foreground parts .. if (m_oversampling == 1) { @@ -711,7 +732,7 @@ LayoutCanvas::paint_event () full_image.set_transparent (true); full_image.fill (0); - bitmaps_to_image (fg_view_op_vector (), fg_bitmap_vector (), dither_pattern (), line_styles (), m_oversampling * dpr (), &full_image, m_viewport_l.width (), m_viewport_l.height (), false, &m_mutex); + bitmaps_to_image (fg_view_op_vector (), fg_bitmap_vector (), dither_pattern (), line_styles (), 1.0 / resolution (), &full_image, m_viewport_l.width (), m_viewport_l.height (), false, &m_mutex); // render the foreground parts .. if (m_oversampling == 1) { @@ -986,13 +1007,13 @@ LayoutCanvas::screenshot () tl::PixelBuffer img (m_viewport.width (), m_viewport.height ()); img.fill (m_background); - DetachedViewObjectCanvas vo_canvas (background_color (), foreground_color (), active_color (), m_viewport_l.width (), m_viewport_l.height (), 1.0 / double (m_oversampling * dpr ()), &img); + DetachedViewObjectCanvas vo_canvas (background_color (), foreground_color (), active_color (), m_viewport_l.width (), m_viewport_l.height (), resolution (), &img); // and paint the background objects. It uses "img" to paint on. do_render_bg (m_viewport_l, vo_canvas); // paint the layout bitmaps - to_image (scaled_view_ops (m_oversampling * dpr ()), dither_pattern (), line_styles (), m_oversampling * dpr (), background_color (), foreground_color (), active_color (), this, *vo_canvas.bg_image (), m_viewport_l.width (), m_viewport_l.height ()); + to_image (scaled_view_ops (1.0 / resolution ()), dither_pattern (), line_styles (), 1.0 / resolution (), background_color (), foreground_color (), active_color (), this, *vo_canvas.bg_image (), m_viewport_l.width (), m_viewport_l.height ()); // subsample current image to provide the background for the foreground objects vo_canvas.make_background (); diff --git a/src/laybasic/laybasic/layLayoutCanvas.h b/src/laybasic/laybasic/layLayoutCanvas.h index ae4f58da2..32100c05d 100644 --- a/src/laybasic/laybasic/layLayoutCanvas.h +++ b/src/laybasic/laybasic/layLayoutCanvas.h @@ -256,6 +256,11 @@ public: */ void set_oversampling (unsigned int os); + /** + * @brief Set high-resolution mode (utilize full DPI on high-DPI displays) + */ + void set_highres_mode (bool hrm); + /** * @brief Sets the depth of the image cache */ @@ -380,6 +385,7 @@ private: lay::LineStyles m_line_styles; std::map > m_scaled_view_ops; unsigned int m_oversampling; + bool m_hrm; double m_gamma; bool m_need_redraw; @@ -417,7 +423,8 @@ private: void do_redraw_all (bool force_redraw = true); void prepare_drawing (); - double dpr (); + double dpr () const; + virtual double resolution () const; const std::vector &scaled_view_ops (unsigned int lw); }; diff --git a/src/laybasic/laybasic/layLayoutViewBase.cc b/src/laybasic/laybasic/layLayoutViewBase.cc index e2ec59bce..4852504d7 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.cc +++ b/src/laybasic/laybasic/layLayoutViewBase.cc @@ -780,6 +780,13 @@ LayoutViewBase::configure (const std::string &name, const std::string &value) mp_canvas->set_oversampling (os); return true; + } else if (name == cfg_highres_mode) { + + bool hrm = false; + tl::from_string (value, hrm); + mp_canvas->set_highres_mode (hrm); + return true; + } else if (name == cfg_image_cache_size) { int sz = 0; diff --git a/src/laybasic/laybasic/layLayoutViewConfig.cc b/src/laybasic/laybasic/layLayoutViewConfig.cc index 3d9f5878b..9ed60be0d 100644 --- a/src/laybasic/laybasic/layLayoutViewConfig.cc +++ b/src/laybasic/laybasic/layLayoutViewConfig.cc @@ -107,6 +107,7 @@ public: options.push_back (std::pair (cfg_drop_small_cells_value, "10")); options.push_back (std::pair (cfg_array_border_instances, "false")); options.push_back (std::pair (cfg_bitmap_oversampling, "1")); + options.push_back (std::pair (cfg_highres_mode, "false")); options.push_back (std::pair (cfg_image_cache_size, "1")); options.push_back (std::pair (cfg_default_font_size, "0")); options.push_back (std::pair (cfg_color_palette, lay::ColorPalette ().to_string ())); diff --git a/src/laybasic/laybasic/layViewObject.h b/src/laybasic/laybasic/layViewObject.h index 34e7207c6..f14c36cdd 100644 --- a/src/laybasic/laybasic/layViewObject.h +++ b/src/laybasic/laybasic/layViewObject.h @@ -966,7 +966,7 @@ public: /** * @brief Gets the QWidget representing this canvas visually in Qt */ - QWidget *widget () + QWidget *widget () const { return mp_widget; } diff --git a/src/laybasic/laybasic/laybasicConfig.h b/src/laybasic/laybasic/laybasicConfig.h index 0b84bf38f..ff816a0d8 100644 --- a/src/laybasic/laybasic/laybasicConfig.h +++ b/src/laybasic/laybasic/laybasicConfig.h @@ -120,6 +120,7 @@ static const std::string cfg_reader_options_show_always ("reader-options-show-al static const std::string cfg_tip_window_hidden ("tip-window-hidden"); static const std::string cfg_bitmap_oversampling ("bitmap-oversampling"); +static const std::string cfg_highres_mode ("highres-mode"); static const std::string cfg_image_cache_size ("image-cache-size"); static const std::string cfg_default_font_size ("default-font-size"); diff --git a/src/layui/layui/LayoutViewConfigPage7.ui b/src/layui/layui/LayoutViewConfigPage7.ui index ae9b834a9..c5f922f10 100644 --- a/src/layui/layui/LayoutViewConfigPage7.ui +++ b/src/layui/layui/LayoutViewConfigPage7.ui @@ -7,7 +7,7 @@ 0 0 791 - 385 + 403 @@ -32,7 +32,7 @@ - Oversampling + Display Quality @@ -53,7 +53,14 @@ - Use oversampling: + Oversampling mode + + + + + + + High resolution mode utilizes the full pixel density on high-DPI displays. Features may look small but rich in details. @@ -99,6 +106,13 @@ + + + + High resolution mode enabled + + + diff --git a/src/layui/layui/layLayoutViewConfigPages.cc b/src/layui/layui/layLayoutViewConfigPages.cc index ab8406b8e..84ff043fa 100644 --- a/src/layui/layui/layLayoutViewConfigPages.cc +++ b/src/layui/layui/layLayoutViewConfigPages.cc @@ -1476,6 +1476,10 @@ LayoutViewConfigPage7::setup (lay::Dispatcher *root) root->config_get (cfg_bitmap_oversampling, oversampling); mp_ui->oversampling->setCurrentIndex (oversampling - 1); + bool highres_mode = false; + root->config_get (cfg_highres_mode, highres_mode); + mp_ui->highres_mode->setChecked (highres_mode); + int default_font_size = 0; root->config_get (cfg_default_font_size, default_font_size); mp_ui->default_font_size->setCurrentIndex (default_font_size); @@ -1499,6 +1503,7 @@ void LayoutViewConfigPage7::commit (lay::Dispatcher *root) { root->config_set (cfg_bitmap_oversampling, mp_ui->oversampling->currentIndex () + 1); + root->config_set (cfg_highres_mode, mp_ui->highres_mode->isChecked ()); root->config_set (cfg_default_font_size, mp_ui->default_font_size->currentIndex ()); root->config_set (cfg_global_trans, db::DCplxTrans (db::DFTrans (mp_ui->global_trans->currentIndex ())).to_string ()); root->config_set (cfg_initial_hier_depth, mp_ui->def_depth->value ()); From 1bff5c1ac3857a333ac74d93127d57555512e4e5 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 1 Oct 2022 19:20:06 +0200 Subject: [PATCH 22/54] WIP: layer icons now follow oversampling and high-resolution mode, new GSI method to fetch layer icon: LayoutView#icon_for_layer --- src/edt/edt/edtMainService.cc | 1 + .../laybasic/gsiDeclLayLayoutViewBase.cc | 11 ++ src/laybasic/laybasic/layLayoutCanvas.cc | 143 +------------- src/laybasic/laybasic/layLayoutCanvas.h | 18 +- src/laybasic/laybasic/layLayoutViewBase.cc | 176 ++++++++++++++++++ src/laybasic/laybasic/layLayoutViewBase.h | 12 ++ src/layui/layui/layLayerControlPanel.cc | 20 ++ src/layui/layui/layLayerControlPanel.h | 12 ++ src/layui/layui/layLayerTreeModel.cc | 167 +---------------- src/layui/layui/layLayerTreeModel.h | 6 +- src/layview/layview/layLayoutView_qt.cc | 14 ++ src/tl/tl/tlPixelBuffer.cc | 136 ++++++++++++++ src/tl/tl/tlPixelBuffer.h | 22 +++ 13 files changed, 434 insertions(+), 304 deletions(-) diff --git a/src/edt/edt/edtMainService.cc b/src/edt/edt/edtMainService.cc index f2a91af17..0f97e0166 100644 --- a/src/edt/edt/edtMainService.cc +++ b/src/edt/edt/edtMainService.cc @@ -30,6 +30,7 @@ #include "laySelector.h" #include "layFinder.h" #include "layLayerProperties.h" +#include "laybasicConfig.h" #include "tlProgress.h" #include "edtPlugin.h" #include "edtMainService.h" diff --git a/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc b/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc index cef7e30bf..db6c1c5d5 100644 --- a/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc +++ b/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc @@ -1603,6 +1603,17 @@ LAYBASIC_PUBLIC Class decl_LayoutViewBase ("lay", "LayoutVi "Returns an array of \\LayerPropertiesIterator objects pointing to the currently selected layers. " "If no layer view is selected currently, an empty array is returned.\n" ) + + gsi::method ("icon_for_layer", &lay::LayoutViewBase::icon_for_layer, gsi::arg ("iter"), gsi::arg ("w"), gsi::arg ("h"), gsi::arg ("dpr"), gsi::arg ("di_off", 0), gsi::arg ("no_state", false), + "@brief Creates an icon pixmap for the given layer.\n" + "\n" + "The icon will have size w times h pixels multiplied by the device pixel ratio (dpr). The dpr is " + "The number of physical pixels per logical pixels on high-DPI displays.\n" + "\n" + "'di_off' will shift the dither pattern by the given number of (physical) pixels. " + "If 'no_state' is true, the icon will not reflect visibility or validity states but rather the display style.\n" + "\n" + "This method has been introduced in version 0.28." + ) + gsi::event ("on_active_cellview_changed", static_cast (&lay::LayoutViewBase::active_cellview_changed_event), "@brief An event indicating that the active cellview has changed\n" "\n" diff --git a/src/laybasic/laybasic/layLayoutCanvas.cc b/src/laybasic/laybasic/layLayoutCanvas.cc index ad89580fa..4ae778d95 100644 --- a/src/laybasic/laybasic/layLayoutCanvas.cc +++ b/src/laybasic/laybasic/layLayoutCanvas.cc @@ -131,137 +131,6 @@ std::string ImageCacheEntry::to_string () const // ---------------------------------------------------------------------------- -static void -blowup (const tl::PixelBuffer &src, tl::PixelBuffer &dest, unsigned int os) -{ - unsigned int ymax = src.height (); - unsigned int xmax = src.width (); - - for (unsigned int y = 0; y < ymax; ++y) { - for (unsigned int i = 0; i < os; ++i) { - const uint32_t *psrc = (const uint32_t *) src.scan_line (y); - uint32_t *pdest = (uint32_t *) dest.scan_line (y * os + i); - for (unsigned int x = 0; x < xmax; ++x) { - for (unsigned int j = 0; j < os; ++j) { - *pdest++ = *psrc; - } - ++psrc; - } - } - } -} - -static void -subsample (const tl::PixelBuffer &src, tl::PixelBuffer &dest, unsigned int os, double g) -{ - // TODO: this is probably not compatible with the endianess of SPARC .. - - // LUT's for combining the RGB channels - - // forward transformation table - unsigned short lut1[256]; - for (unsigned int i = 0; i < 256; ++i) { - double f = (65536 / (os * os)) - 1; - lut1[i] = (unsigned short)std::min (f, std::max (0.0, floor (0.5 + pow (i / 255.0, g) * f))); - } - - // backward transformation table - unsigned char lut2[65536]; - for (unsigned int i = 0; i < 65536; ++i) { - double f = os * os * ((65536 / (os * os)) - 1); - lut2[i] = (unsigned char)std::min (255.0, std::max (0.0, floor (0.5 + pow (i / f, 1.0 / g) * 255.0))); - } - - // LUT's for alpha channel - - // forward transformation table - unsigned short luta1[256]; - for (unsigned int i = 0; i < 256; ++i) { - double f = (65536 / (os * os)) - 1; - luta1[i] = (unsigned short)std::min (f, std::max (0.0, floor (0.5 + (i / 255.0) * f))); - } - - // backward transformation table - unsigned char luta2[65536]; - for (unsigned int i = 0; i < 65536; ++i) { - double f = os * os * ((65536 / (os * os)) - 1); - luta2[i] = (unsigned char)std::min (255.0, std::max (0.0, floor (0.5 + (i / f) * 255.0))); - } - - unsigned int ymax = dest.height (); - unsigned int xmax = dest.width (); - - unsigned short *buffer = new unsigned short[xmax * 4]; - - for (unsigned int y = 0; y < ymax; ++y) { - - { - - const unsigned char *psrc = (const unsigned char *) src.scan_line (y * os); - unsigned short *pdest = buffer; - - for (unsigned int x = 0; x < xmax; ++x) { - - pdest[0] = lut1[psrc[0]]; - pdest[1] = lut1[psrc[1]]; - pdest[2] = lut1[psrc[2]]; - pdest[3] = luta1[psrc[3]]; - psrc += 4; - - for (unsigned int j = os; j > 1; j--) { - pdest[0] += lut1[psrc[0]]; - pdest[1] += lut1[psrc[1]]; - pdest[2] += lut1[psrc[2]]; - pdest[3] += luta1[psrc[3]]; - psrc += 4; - } - - pdest += 4; - - } - - } - - for (unsigned int i = 1; i < os; ++i) { - - const unsigned char *psrc = (const unsigned char *) src.scan_line (y * os + i); - unsigned short *pdest = buffer; - - for (unsigned int x = 0; x < xmax; ++x) { - - for (unsigned int j = os; j > 0; j--) { - pdest[0] += lut1[psrc[0]]; - pdest[1] += lut1[psrc[1]]; - pdest[2] += lut1[psrc[2]]; - pdest[3] += luta1[psrc[3]]; - psrc += 4; - } - - pdest += 4; - - } - - } - - { - - unsigned char *pdest = (unsigned char *) dest.scan_line (y); - const unsigned short *psrc = buffer; - - for (unsigned int x = 0; x < xmax; ++x) { - *pdest++ = lut2[*psrc++]; - *pdest++ = lut2[*psrc++]; - *pdest++ = lut2[*psrc++]; - *pdest++ = luta2[*psrc++]; - } - - } - - } - - delete[] buffer; -} - void invert (unsigned char *data, unsigned int width, unsigned int height) { @@ -693,7 +562,7 @@ LayoutCanvas::paint_event () } else { tl::PixelBuffer subsampled_image (m_viewport.width (), m_viewport.height ()); subsampled_image.set_transparent (mp_image->transparent ()); - subsample (full_image, subsampled_image, m_oversampling, m_gamma); + full_image.subsample (subsampled_image, m_oversampling, m_gamma); *mp_image_fg = subsampled_image; } @@ -705,7 +574,7 @@ LayoutCanvas::paint_event () tl::PixelBuffer subsampled_image (m_viewport.width (), m_viewport.height ()); subsampled_image.set_transparent (mp_image->transparent ()); - subsample (*mp_image, subsampled_image, m_oversampling, m_gamma); + mp_image->subsample (subsampled_image, m_oversampling, m_gamma); *mp_image_fg = subsampled_image; } @@ -744,7 +613,7 @@ LayoutCanvas::paint_event () } else { tl::PixelBuffer subsampled_image (m_viewport.width (), m_viewport.height ()); subsampled_image.set_transparent (true); - subsample (full_image, subsampled_image, m_oversampling, m_gamma); + full_image.subsample (subsampled_image, m_oversampling, m_gamma); QImage img = subsampled_image.to_image (); #if QT_VERSION >= 0x050000 img.setDevicePixelRatio (dpr ()); @@ -820,9 +689,9 @@ public: { if (mp_image_l) { unsigned int os = mp_image_l->width () / width; - blowup (*mp_image, *mp_image_l, os); + mp_image->blowup (*mp_image_l, os); bitmaps_to_image (fg_view_op_vector (), fg_bitmap_vector (), dp, ls, 1.0 / resolution (), mp_image_l, mp_image_l->width (), mp_image_l->height (), false, 0); - subsample (*mp_image_l, *mp_image, os, m_gamma); + mp_image_l->subsample (*mp_image, os, m_gamma); } else { bitmaps_to_image (fg_view_op_vector (), fg_bitmap_vector (), dp, ls, 1.0 / resolution (), mp_image, width, height, false, 0); } @@ -833,7 +702,7 @@ public: { if (mp_image_l && mp_image->width () > 0) { unsigned int os = mp_image_l->width () / mp_image->width (); - subsample (*mp_image_l, *mp_image, os, m_gamma); + mp_image_l->subsample (*mp_image, os, m_gamma); } } diff --git a/src/laybasic/laybasic/layLayoutCanvas.h b/src/laybasic/laybasic/layLayoutCanvas.h index 32100c05d..d73f614b2 100644 --- a/src/laybasic/laybasic/layLayoutCanvas.h +++ b/src/laybasic/laybasic/layLayoutCanvas.h @@ -257,10 +257,26 @@ public: void set_oversampling (unsigned int os); /** - * @brief Set high-resolution mode (utilize full DPI on high-DPI displays) + * @brief Gets the oversampling factor + */ + unsigned int oversampling () const + { + return m_oversampling; + } + + /** + * @brief Set high resolution mode (utilize full DPI on high-DPI displays) */ void set_highres_mode (bool hrm); + /** + * @brief Gets the high resolution mode flag + */ + bool highres_mode () const + { + return m_hrm; + } + /** * @brief Sets the depth of the image cache */ diff --git a/src/laybasic/laybasic/layLayoutViewBase.cc b/src/laybasic/laybasic/layLayoutViewBase.cc index 4852504d7..dc176a9e5 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.cc +++ b/src/laybasic/laybasic/layLayoutViewBase.cc @@ -36,6 +36,7 @@ #include "tlExceptions.h" #include "tlDeferredExecution.h" #include "layLayoutViewBase.h" +#include "layBitmapsToImage.h" #include "layViewOp.h" #include "layViewObject.h" #include "layConverters.h" @@ -1527,6 +1528,181 @@ LayoutViewBase::set_selected_layers (const std::vector view_ops; + view_ops.push_back (view_op); + + std::vector pbitmaps; + pbitmaps.push_back (&bitmap); + + lay::bitmaps_to_image (view_ops, pbitmaps, dither_pattern, line_styles, dpr, pimage, width, height, false, 0); +} + +tl::PixelBuffer +LayoutViewBase::icon_for_layer (const LayerPropertiesConstIterator &iter, unsigned int w, unsigned int h, double dpr, unsigned int di_off, bool no_state) +{ + int oversampling = canvas () ? canvas ()->oversampling () : 1; + double gamma = 2.0; + + bool hrm = canvas () ? canvas ()->highres_mode () : false; + double dpr_drawing = oversampling * (hrm ? 1.0 : dpr); + + h = std::max ((unsigned int) 16, h) * oversampling * dpr + 0.5; + w = std::max ((unsigned int) 16, w) * oversampling * dpr + 0.5; + + tl::color_t def_color = 0x808080; + tl::color_t fill_color = iter->has_fill_color (true) ? iter->eff_fill_color (true) : def_color; + tl::color_t frame_color = iter->has_frame_color (true) ? iter->eff_frame_color (true) : def_color; + + tl::PixelBuffer image (w, h); + image.set_transparent (true); + image.fill (background_color ().rgb ()); + + // upper scanline is a dummy one + tl::color_t *sl0 = (uint32_t *) image.scan_line (0); + for (size_t i = 0; i < w; ++i) { + *sl0++ = 0; + } + + lay::Bitmap fill (w, h, 1.0); + lay::Bitmap frame (w, h, 1.0); + lay::Bitmap text (w, h, 1.0); + lay::Bitmap vertex (w, h, 1.0); + + unsigned int wp = w - 1; + + if (! no_state && ! iter->visible (true)) { + + wp = w / 4; + + // Show the arrow if it is invisible also locally. + if (! iter->visible (false)) { + + unsigned int aw = h / 4; + unsigned int ap = w / 2 - 1; + for (unsigned int i = 0; i <= aw; ++i) { + text.fill (h / 2 - 1 - i, ap, ap + aw - i + 1); + text.fill (h / 2 - 1 + i, ap, ap + aw - i + 1); + } + + } + + } + + if (! no_state && no_stipples ()) { + // Show a partial stipple pattern only for "no stipple" mode + for (unsigned int i = 1; i < h - 2; ++i) { + fill.fill (i, w - 1 - w / 4, w); + } + } else { + for (unsigned int i = 1; i < h - 2; ++i) { + fill.fill (i, w - 1 - wp, w); + } + } + + int lw = iter->width (true); + if (lw < 0) { + // default line width is 0 for parents and 1 for leafs + lw = iter->has_children () ? 0 : 1; + } + lw = lw * dpr_drawing + 0.5; + + int p0 = lw / 2; + p0 = std::max (0, std::min (int (w / 4 - 1), p0)); + + int p1 = (lw - 1) / 2; + p1 = std::max (0, std::min (int (w / 4 - 1), p1)); + + int p0x = p0, p1x = p1; + unsigned int ddx = 0; + unsigned int ddy = h - 2 - p1 - p0; + if (iter->xfill (true)) { + ddx = wp - p0 - p1 - 1; + } + unsigned int d = ddx / 2; + + frame.fill (p0, w - 1 - (wp - p1), w); + frame.fill (h - 2 - p1, w - 1 - (wp - p1), w); + + for (unsigned int i = p0; i < h - 2; ++i) { + + frame.fill (i, w - 1 - p0, w - p0); + frame.fill (i, w - 1 - (wp - p1), w - (wp - p1)); + frame.fill (i, w - 1 - p0x, w - p0x); + frame.fill (i, w - 1 - (wp - p1x), w - (wp - p1x)); + + while (d < ddx) { + d += ddy; + frame.fill (i, w - 1 - p0x, w - p0x); + frame.fill (i, w - 1 - (wp - p1x), w - (wp - p1x)); + ++p0x; + ++p1x; + } + + if (d >= ddx) { + d -= ddx; + } + + } + + if (! no_state && ! iter->valid (true)) { + + unsigned int bp = w - 1 - ((w * 7) / 8 - 1); + unsigned int be = bp + h / 2; + unsigned int bw = h / 4 - 1; + unsigned int by = h / 2 - 1; + + for (unsigned int i = 0; i < bw + 2; ++i) { + fill.clear (by - i, bp - 1, be); + fill.clear (by + i, bp - 1, be); + } + + for (unsigned int i = 0; i < bw; ++i) { + text.fill (by - i, bp + bw - i - 1, bp + bw - i + 1); + text.fill (by - i - 1, bp + bw - i - 1, bp + bw - i + 1); + text.fill (by - i, bp + bw + i, bp + bw + i + 2); + text.fill (by - i - 1, bp + bw + i, bp + bw + i + 2); + text.fill (by + i, bp + bw - i - 1, bp + bw - i + 1); + text.fill (by + i + 1, bp + bw - i - 1, bp + bw - i + 1); + text.fill (by + i, bp + bw + i, bp + bw + i + 2); + text.fill (by + i + 1, bp + bw + i, bp + bw + i + 2); + } + + } + + vertex.fill (h / 2 - 1, w - 1 - wp / 2, w - wp / 2); + + lay::ViewOp::Mode mode = lay::ViewOp::Copy; + + // create fill + single_bitmap_to_image (lay::ViewOp (fill_color, mode, 0, iter->eff_dither_pattern (true), di_off), fill, &image, dither_pattern (), line_styles (), dpr_drawing, w, h); + // create frame + if (lw == 0) { + single_bitmap_to_image (lay::ViewOp (frame_color, mode, 0 /*solid line*/, 2 /*dotted*/, 0), frame, &image, dither_pattern (), line_styles (), dpr_drawing, w, h); + } else { + single_bitmap_to_image (lay::ViewOp (frame_color, mode, iter->eff_line_style (true), 0, 0, lay::ViewOp::Rect, lw), frame, &image, dither_pattern (), line_styles (), dpr_drawing, w, h); + } + // create text + single_bitmap_to_image (lay::ViewOp (frame_color, mode, 0, 0, 0), text, &image, dither_pattern (), line_styles (), dpr_drawing, w, h); + // create vertex + single_bitmap_to_image (lay::ViewOp (frame_color, mode, 0, 0, 0, lay::ViewOp::Cross, iter->marked (true) ? 9/*mark size*/ : 0), vertex, &image, dither_pattern (), line_styles (), dpr_drawing, w, h); + + if (oversampling > 1) { + tl::PixelBuffer subsampled (image.width () / oversampling, image.height () / oversampling); + image.subsample (subsampled, oversampling, gamma); + return subsampled; + } else { + return image; + } +} + void LayoutViewBase::merge_dither_pattern (lay::LayerPropertiesList &props) { diff --git a/src/laybasic/laybasic/layLayoutViewBase.h b/src/laybasic/laybasic/layLayoutViewBase.h index d34426ce8..9f7b188d5 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.h +++ b/src/laybasic/laybasic/layLayoutViewBase.h @@ -581,6 +581,18 @@ public: */ virtual std::vector selected_layers () const; + /** + * @brief Gets a pixmap representing the given layer + * + * @param iter indicates the layer + * @param w The width in logical pixels of the generated pixmap (will be multiplied by dpr) + * @param h The height in logical pixels of the generated pixmap (will be multiplied by dpr) + * @param dpr The device pixel ratio (number of image pixes per logical pixel) + * @param di_off The dither pattern offset (used for animation) + * @param no_state If true, the state will not be indicated + */ + tl::PixelBuffer icon_for_layer (const lay::LayerPropertiesConstIterator &iter, unsigned int w, unsigned int h, double dpr, unsigned int di_off, bool no_state); + /** * @brief Sets the layers that are selected in the layer browser */ diff --git a/src/layui/layui/layLayerControlPanel.cc b/src/layui/layui/layLayerControlPanel.cc index 5b1ff584c..1ff0370c8 100644 --- a/src/layui/layui/layLayerControlPanel.cc +++ b/src/layui/layui/layLayerControlPanel.cc @@ -208,6 +208,8 @@ LayerControlPanel::LayerControlPanel (lay::LayoutViewBase *view, db::Manager *ma m_hidden_flags_need_update (true), m_in_update (false), m_phase (0), + m_oversampling (1), + m_hrm (false), m_do_update_content_dm (this, &LayerControlPanel::do_update_content), m_no_stipples (false) { @@ -1712,6 +1714,24 @@ LayerControlPanel::set_phase (int phase) } } +void +LayerControlPanel::set_highres_mode (bool hrm) +{ + if (m_hrm != hrm) { + m_hrm = hrm; + m_do_update_content_dm (); + } +} + +void +LayerControlPanel::set_oversampling (int os) +{ + if (m_oversampling != os) { + m_oversampling = os; + m_do_update_content_dm (); + } +} + static void set_hidden_flags_rec (LayerTreeModel *model, QTreeView *tree_view, const QModelIndex &parent) { diff --git a/src/layui/layui/layLayerControlPanel.h b/src/layui/layui/layLayerControlPanel.h index 93aad12b6..87920ff57 100644 --- a/src/layui/layui/layLayerControlPanel.h +++ b/src/layui/layui/layLayerControlPanel.h @@ -208,6 +208,16 @@ public: */ void set_phase (int phase); + /** + * @brief Sets highres mode + */ + void set_highres_mode (bool hrm); + + /** + * @brief Sets oversampling mode + */ + void set_oversampling (int os); + /** * @brief Tell, if the model has been updated already (true) or if it is still under construction (false) */ @@ -347,6 +357,8 @@ private: bool m_in_update; std::vector m_new_sel; int m_phase; + int m_oversampling; + bool m_hrm; tl::DeferredMethod m_do_update_content_dm; std::set m_expanded; bool m_no_stipples; diff --git a/src/layui/layui/layLayerTreeModel.cc b/src/layui/layui/layLayerTreeModel.cc index fe3f1a97f..b2a284a11 100644 --- a/src/layui/layui/layLayerTreeModel.cc +++ b/src/layui/layui/layLayerTreeModel.cc @@ -24,7 +24,6 @@ #include "layLayerTreeModel.h" #include "layLayoutViewBase.h" -#include "layBitmapsToImage.h" #include "dbLayoutUtils.h" #include "tlLog.h" #include "tlTimer.h" @@ -182,7 +181,8 @@ EmptyWithinViewCache::determine_empty_layers (const db::Layout *layout, unsigned LayerTreeModel::LayerTreeModel (QWidget *parent, lay::LayoutViewBase *view) : QAbstractItemModel (parent), mp_parent (parent), mp_view (view), m_filter_mode (false), m_id_start (0), m_id_end (0), - m_phase ((unsigned int) -1), m_test_shapes_in_view (false), m_hide_empty_layers (false) + m_phase ((unsigned int) -1), + m_test_shapes_in_view (false), m_hide_empty_layers (false) { // .. nothing yet .. } @@ -620,170 +620,11 @@ LayerTreeModel::empty_within_view_predicate (const QModelIndex &index) const } } -/** - * @brief A helper function to create an image from a single bitmap - */ -static void -single_bitmap_to_image (const lay::ViewOp &view_op, lay::Bitmap &bitmap, - tl::PixelBuffer *pimage, const lay::DitherPattern &dither_pattern, const lay::LineStyles &line_styles, - double dpr, unsigned int width, unsigned int height) -{ - std::vector view_ops; - view_ops.push_back (view_op); - - std::vector pbitmaps; - pbitmaps.push_back (&bitmap); - - lay::bitmaps_to_image (view_ops, pbitmaps, dither_pattern, line_styles, dpr, pimage, width, height, false, 0); -} - -LAYUI_PUBLIC QIcon LayerTreeModel::icon_for_layer (const lay::LayerPropertiesConstIterator &iter, lay::LayoutViewBase *view, unsigned int w, unsigned int h, double dpr, unsigned int di_off, bool no_state) { - h = std::max ((unsigned int) 16, h) * dpr + 0.5; - w = std::max ((unsigned int) 16, w) * dpr + 0.5; - - tl::color_t def_color = 0x808080; - tl::color_t fill_color = iter->has_fill_color (true) ? iter->eff_fill_color (true) : def_color; - tl::color_t frame_color = iter->has_frame_color (true) ? iter->eff_frame_color (true) : def_color; - - tl::PixelBuffer image (w, h); - image.set_transparent (true); - image.fill (view->background_color ().rgb ()); - - // upper scanline is a dummy one - uint32_t *sl0 = (uint32_t *) image.scan_line (0); - uint32_t transparent = QColor (Qt::transparent).rgba (); - for (size_t i = 0; i < w; ++i) { - *sl0++ = transparent; - } - - // TODO: adjust the resolution according to the oversampling mode - lay::Bitmap fill (w, h, 1.0); - lay::Bitmap frame (w, h, 1.0); - lay::Bitmap text (w, h, 1.0); - lay::Bitmap vertex (w, h, 1.0); - - unsigned int wp = w - 1; - - if (! no_state && ! iter->visible (true)) { - - wp = w / 4; - - // Show the arrow if it is invisible also locally. - if (! iter->visible (false)) { - - unsigned int aw = h / 4; - unsigned int ap = w / 2 - 1; - for (unsigned int i = 0; i <= aw; ++i) { - text.fill (h / 2 - 1 - i, ap, ap + aw - i + 1); - text.fill (h / 2 - 1 + i, ap, ap + aw - i + 1); - } - - } - - } - - if (! no_state && view->no_stipples ()) { - // Show a partial stipple pattern only for "no stipple" mode - for (unsigned int i = 1; i < h - 2; ++i) { - fill.fill (i, w - 1 - w / 4, w); - } - } else { - for (unsigned int i = 1; i < h - 2; ++i) { - fill.fill (i, w - 1 - wp, w); - } - } - - int lw = iter->width (true); - if (lw < 0) { - // default line width is 0 for parents and 1 for leafs - lw = iter->has_children () ? 0 : 1; - } - lw = lw * dpr + 0.5; - - int p0 = lw / 2; - p0 = std::max (0, std::min (int (w / 4 - 1), p0)); - - int p1 = (lw - 1) / 2; - p1 = std::max (0, std::min (int (w / 4 - 1), p1)); - - int p0x = p0, p1x = p1; - unsigned int ddx = 0; - unsigned int ddy = h - 2 - p1 - p0; - if (iter->xfill (true)) { - ddx = wp - p0 - p1 - 1; - } - unsigned int d = ddx / 2; - - frame.fill (p0, w - 1 - (wp - p1), w); - frame.fill (h - 2 - p1, w - 1 - (wp - p1), w); - - for (unsigned int i = p0; i < h - 2; ++i) { - - frame.fill (i, w - 1 - p0, w - p0); - frame.fill (i, w - 1 - (wp - p1), w - (wp - p1)); - frame.fill (i, w - 1 - p0x, w - p0x); - frame.fill (i, w - 1 - (wp - p1x), w - (wp - p1x)); - - while (d < ddx) { - d += ddy; - frame.fill (i, w - 1 - p0x, w - p0x); - frame.fill (i, w - 1 - (wp - p1x), w - (wp - p1x)); - ++p0x; - ++p1x; - } - - if (d >= ddx) { - d -= ddx; - } - - } - - if (! no_state && ! iter->valid (true)) { - - unsigned int bp = w - 1 - ((w * 7) / 8 - 1); - unsigned int be = bp + h / 2; - unsigned int bw = h / 4 - 1; - unsigned int by = h / 2 - 1; - - for (unsigned int i = 0; i < bw + 2; ++i) { - fill.clear (by - i, bp - 1, be); - fill.clear (by + i, bp - 1, be); - } - - for (unsigned int i = 0; i < bw; ++i) { - text.fill (by - i, bp + bw - i - 1, bp + bw - i + 1); - text.fill (by - i - 1, bp + bw - i - 1, bp + bw - i + 1); - text.fill (by - i, bp + bw + i, bp + bw + i + 2); - text.fill (by - i - 1, bp + bw + i, bp + bw + i + 2); - text.fill (by + i, bp + bw - i - 1, bp + bw - i + 1); - text.fill (by + i + 1, bp + bw - i - 1, bp + bw - i + 1); - text.fill (by + i, bp + bw + i, bp + bw + i + 2); - text.fill (by + i + 1, bp + bw + i, bp + bw + i + 2); - } - - } - - vertex.fill (h / 2 - 1, w - 1 - wp / 2, w - wp / 2); - - lay::ViewOp::Mode mode = lay::ViewOp::Copy; - - // create fill - single_bitmap_to_image (lay::ViewOp (fill_color, mode, 0, iter->eff_dither_pattern (true), di_off), fill, &image, view->dither_pattern (), view->line_styles (), dpr, w, h); - // create frame - if (lw == 0) { - single_bitmap_to_image (lay::ViewOp (frame_color, mode, 0 /*solid line*/, 2 /*dotted*/, 0), frame, &image, view->dither_pattern (), view->line_styles (), dpr, w, h); - } else { - single_bitmap_to_image (lay::ViewOp (frame_color, mode, iter->eff_line_style (true), 0, 0, lay::ViewOp::Rect, lw), frame, &image, view->dither_pattern (), view->line_styles (), dpr, w, h); - } - // create text - single_bitmap_to_image (lay::ViewOp (frame_color, mode, 0, 0, 0), text, &image, view->dither_pattern (), view->line_styles (), dpr, w, h); - // create vertex - single_bitmap_to_image (lay::ViewOp (frame_color, mode, 0, 0, 0, lay::ViewOp::Cross, iter->marked (true) ? 9/*mark size*/ : 0), vertex, &image, view->dither_pattern (), view->line_styles (), dpr, w, h); - - QPixmap pixmap = QPixmap::fromImage (image.to_image ()); + tl::PixelBuffer px = view->icon_for_layer (iter, w, h, dpr, di_off, no_state); + QPixmap pixmap = QPixmap::fromImage (px.to_image ()); #if QT_VERSION >= 0x050000 pixmap.setDevicePixelRatio (dpr); #endif diff --git a/src/layui/layui/layLayerTreeModel.h b/src/layui/layui/layLayerTreeModel.h index 902166bdd..fbd548814 100644 --- a/src/layui/layui/layLayerTreeModel.h +++ b/src/layui/layui/layLayerTreeModel.h @@ -123,17 +123,17 @@ public: QModelIndex index (lay::LayerPropertiesConstIterator iter, int column) const; /** - * @brief Convert a QModelIndex to an iterator + * @brief Converts a QModelIndex to an iterator */ lay::LayerPropertiesConstIterator iterator (const QModelIndex &index) const; /** - * @brief Get a flag indicating that an entry is hidden + * @brief Gets a flag indicating that an entry is hidden */ bool is_hidden (const QModelIndex &index) const; /** - * @brief Set the animation phase + * @brief Sets the animation phase */ void set_phase (unsigned int ph); diff --git a/src/layview/layview/layLayoutView_qt.cc b/src/layview/layview/layLayoutView_qt.cc index e25e42ebb..f99154707 100644 --- a/src/layview/layview/layLayoutView_qt.cc +++ b/src/layview/layview/layLayoutView_qt.cc @@ -731,6 +731,20 @@ LayoutView::update_menu (lay::LayoutView *view, lay::AbstractMenu &menu) bool LayoutView::configure (const std::string &name, const std::string &value) { + if (name == cfg_bitmap_oversampling) { + + int os = 1; + tl::from_string (value, os); + mp_control_panel->set_oversampling (os); + + } else if (name == cfg_highres_mode) { + + bool hrm = false; + tl::from_string (value, hrm); + mp_control_panel->set_highres_mode (hrm); + + } + if (LayoutViewBase::configure (name, value)) { return true; } diff --git a/src/tl/tl/tlPixelBuffer.cc b/src/tl/tl/tlPixelBuffer.cc index 0c8566ad8..4accba4df 100644 --- a/src/tl/tl/tlPixelBuffer.cc +++ b/src/tl/tl/tlPixelBuffer.cc @@ -29,6 +29,7 @@ #endif #include +#include namespace tl { @@ -358,6 +359,141 @@ PixelBuffer::diff (const PixelBuffer &other) const return res; } +void +PixelBuffer::blowup (tl::PixelBuffer &dest, unsigned int os) +{ + tl_assert (dest.width () == width () * os); + tl_assert (dest.height () == height () * os); + + unsigned int ymax = height (); + unsigned int xmax = width (); + + for (unsigned int y = 0; y < ymax; ++y) { + for (unsigned int i = 0; i < os; ++i) { + const uint32_t *psrc = (const uint32_t *) scan_line (y); + uint32_t *pdest = (uint32_t *) dest.scan_line (y * os + i); + for (unsigned int x = 0; x < xmax; ++x) { + for (unsigned int j = 0; j < os; ++j) { + *pdest++ = *psrc; + } + ++psrc; + } + } + } +} + +void +PixelBuffer::subsample (tl::PixelBuffer &dest, unsigned int os, double g) +{ + // TODO: this is probably not compatible with the endianess of SPARC .. + + // LUT's for combining the RGB channels + + // forward transformation table + unsigned short lut1[256]; + for (unsigned int i = 0; i < 256; ++i) { + double f = (65536 / (os * os)) - 1; + lut1[i] = (unsigned short)std::min (f, std::max (0.0, floor (0.5 + pow (i / 255.0, g) * f))); + } + + // backward transformation table + unsigned char lut2[65536]; + for (unsigned int i = 0; i < 65536; ++i) { + double f = os * os * ((65536 / (os * os)) - 1); + lut2[i] = (unsigned char)std::min (255.0, std::max (0.0, floor (0.5 + pow (i / f, 1.0 / g) * 255.0))); + } + + // LUT's for alpha channel + + // forward transformation table + unsigned short luta1[256]; + for (unsigned int i = 0; i < 256; ++i) { + double f = (65536 / (os * os)) - 1; + luta1[i] = (unsigned short)std::min (f, std::max (0.0, floor (0.5 + (i / 255.0) * f))); + } + + // backward transformation table + unsigned char luta2[65536]; + for (unsigned int i = 0; i < 65536; ++i) { + double f = os * os * ((65536 / (os * os)) - 1); + luta2[i] = (unsigned char)std::min (255.0, std::max (0.0, floor (0.5 + (i / f) * 255.0))); + } + + unsigned int ymax = dest.height (); + unsigned int xmax = dest.width (); + + unsigned short *buffer = new unsigned short[xmax * 4]; + + for (unsigned int y = 0; y < ymax; ++y) { + + { + + const unsigned char *psrc = (const unsigned char *) scan_line (y * os); + unsigned short *pdest = buffer; + + for (unsigned int x = 0; x < xmax; ++x) { + + pdest[0] = lut1[psrc[0]]; + pdest[1] = lut1[psrc[1]]; + pdest[2] = lut1[psrc[2]]; + pdest[3] = luta1[psrc[3]]; + psrc += 4; + + for (unsigned int j = os; j > 1; j--) { + pdest[0] += lut1[psrc[0]]; + pdest[1] += lut1[psrc[1]]; + pdest[2] += lut1[psrc[2]]; + pdest[3] += luta1[psrc[3]]; + psrc += 4; + } + + pdest += 4; + + } + + } + + for (unsigned int i = 1; i < os; ++i) { + + const unsigned char *psrc = (const unsigned char *) scan_line (y * os + i); + unsigned short *pdest = buffer; + + for (unsigned int x = 0; x < xmax; ++x) { + + for (unsigned int j = os; j > 0; j--) { + pdest[0] += lut1[psrc[0]]; + pdest[1] += lut1[psrc[1]]; + pdest[2] += lut1[psrc[2]]; + pdest[3] += luta1[psrc[3]]; + psrc += 4; + } + + pdest += 4; + + } + + } + + { + + unsigned char *pdest = (unsigned char *) dest.scan_line (y); + const unsigned short *psrc = buffer; + + for (unsigned int x = 0; x < xmax; ++x) { + *pdest++ = lut2[*psrc++]; + *pdest++ = lut2[*psrc++]; + *pdest++ = lut2[*psrc++]; + *pdest++ = luta2[*psrc++]; + } + + } + + } + + delete[] buffer; +} + + #if defined(HAVE_PNG) PixelBuffer diff --git a/src/tl/tl/tlPixelBuffer.h b/src/tl/tl/tlPixelBuffer.h index 62b6206f3..0f48d91fb 100644 --- a/src/tl/tl/tlPixelBuffer.h +++ b/src/tl/tl/tlPixelBuffer.h @@ -257,6 +257,28 @@ public: */ PixelBuffer diff (const PixelBuffer &other) const; + /** + * @brief Subsamples the image and puts the subsampled image into the destination image + * + * @param dest Where the subsampled image goes to + * @param os The subsampling factor + * @param g The gamma value for color interpolation + * + * The dimension of the destination image must be set to the corresponding fraction of + * self's dimension. + */ + void subsample (tl::PixelBuffer &dest, unsigned int os, double g); + + /** + * @brief Scales the image into the given destination image + * + * @param dest Where the scaled image goes to + * @param os The scaling factor + * + * The destination images dimension must have been set of self's dimension times os. + */ + void blowup (tl::PixelBuffer &dest, unsigned int os); + /** * @brief Gets the texts * From e4830b98b3713fcfea65358f8e8561085e19bbd4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 1 Oct 2022 19:52:12 +0200 Subject: [PATCH 23/54] Small bugfixes --- src/layui/layui/layLayerTreeModel.cc | 4 ++-- src/layui/layui/layLayoutViewConfigPages.cc | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/layui/layui/layLayerTreeModel.cc b/src/layui/layui/layLayerTreeModel.cc index b2a284a11..61aadcfec 100644 --- a/src/layui/layui/layLayerTreeModel.cc +++ b/src/layui/layui/layLayerTreeModel.cc @@ -623,8 +623,8 @@ LayerTreeModel::empty_within_view_predicate (const QModelIndex &index) const QIcon LayerTreeModel::icon_for_layer (const lay::LayerPropertiesConstIterator &iter, lay::LayoutViewBase *view, unsigned int w, unsigned int h, double dpr, unsigned int di_off, bool no_state) { - tl::PixelBuffer px = view->icon_for_layer (iter, w, h, dpr, di_off, no_state); - QPixmap pixmap = QPixmap::fromImage (px.to_image ()); + QImage img = view->icon_for_layer (iter, w, h, dpr, di_off, no_state).to_image_copy (); + QPixmap pixmap = QPixmap::fromImage (std::move (img)); #if QT_VERSION >= 0x050000 pixmap.setDevicePixelRatio (dpr); #endif diff --git a/src/layui/layui/layLayoutViewConfigPages.cc b/src/layui/layui/layLayoutViewConfigPages.cc index 84ff043fa..609c3df59 100644 --- a/src/layui/layui/layLayoutViewConfigPages.cc +++ b/src/layui/layui/layLayoutViewConfigPages.cc @@ -888,10 +888,10 @@ LayoutViewConfigPage4::update () #endif QPainter painter (&img); - painter.setPen (QPen (palette ().color (QPalette::Active, QPalette::Text), 1.0 / dpr)); - painter.setBrush (QBrush (color)); - QRectF r (0, 0, w - painter.pen ().widthF (), h - painter.pen ().widthF ()); - painter.drawRect (r); + QRectF r (0.0, 0.0, w, h); + painter.fillRect (r, QBrush (palette ().color (QPalette::Active, QPalette::ButtonText))); + r = QRectF (1.0, 1.0, w - 2.0, h - 2.0); + painter.fillRect (r, QBrush (color)); painter.setFont (font ()); painter.setPen (QPen (text_color)); painter.drawText (r, Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextSingleLine, text); From 914ad98858f3c425fadc009e04f2483b4648c21c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 1 Oct 2022 21:59:34 +0200 Subject: [PATCH 24/54] Ruler edge marker is now visible also for scaling > 1 --- src/laybasic/laybasic/layEditorServiceBase.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/laybasic/laybasic/layEditorServiceBase.cc b/src/laybasic/laybasic/layEditorServiceBase.cc index da500822b..1eca0e107 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.cc +++ b/src/laybasic/laybasic/layEditorServiceBase.cc @@ -160,7 +160,7 @@ protected: ops[0] = lay::ViewOp (cursor_color (canvas), lay::ViewOp::Copy, solid_style, 0, 0, lay::ViewOp::Rect, lw, 0); lay::CanvasPlane *arrow_plane = canvas.plane (ops); - ops[0] = lay::ViewOp (cursor_color (canvas), lay::ViewOp::Copy, m_solid ? solid_style : dashed_style, 1, 0, lay::ViewOp::Rect, lw, 0); + ops[0] = lay::ViewOp (cursor_color (canvas), lay::ViewOp::Copy, m_solid ? solid_style : dashed_style, 0, 0, lay::ViewOp::Rect, lw, 0); lay::CanvasPlane *edge_plane = canvas.plane (ops); lay::Renderer &r = canvas.renderer (); From 3b6ef57ddcda7874b822a72b04a04f20f7930d1b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 1 Oct 2022 23:22:36 +0200 Subject: [PATCH 25/54] Cross size in marked mode now scales with resolution --- src/laybasic/laybasic/layBitmapsToImage.cc | 143 ++++++++++++++------- src/laybasic/laybasic/layLayoutViewBase.cc | 2 +- 2 files changed, 95 insertions(+), 50 deletions(-) diff --git a/src/laybasic/laybasic/layBitmapsToImage.cc b/src/laybasic/laybasic/layBitmapsToImage.cc index 17e062233..b4cc038f1 100644 --- a/src/laybasic/laybasic/layBitmapsToImage.cc +++ b/src/laybasic/laybasic/layBitmapsToImage.cc @@ -311,15 +311,22 @@ render_scanline_cross (const uint32_t *dp, unsigned int ds, const lay::Bitmap *p return; } - if (pixels > 15) { - pixels = 15; + // NOTE: hardcoded bar/width ratio for crosses. + unsigned int lw = std::min ((unsigned int) 6, pixels / 9); + + const int max_pixels = 31; + if (pixels > max_pixels) { + pixels = max_pixels; } const uint32_t *dm = dp; unsigned int px1 = (pixels - 1) / 2; unsigned int px2 = (pixels - 1) - px1; - const uint32_t *ps[16]; + unsigned int spx1 = (lw - 1) / 2; + unsigned int spx2 = (lw - 1) - spx1; + + const uint32_t *ps[max_pixels + 1]; for (unsigned int p = 0; p < pixels; ++p) { if (y + p < px1) { ps[p] = pbitmap->scanline (0); @@ -330,56 +337,94 @@ render_scanline_cross (const uint32_t *dp, unsigned int ds, const lay::Bitmap *p } } - uint32_t d, dd = 0, dn; - dn = *(ps[px1]++); + uint32_t *dpp = data; + for (unsigned int i = (w + lay::wordlen - 1) / lay::wordlen; i > 0; --i) { + *dpp++ = 0; + } - unsigned int x = w; - while (true) { + for (unsigned int o = 0; o < pixels; ++o) { - d = dn; + dpp = data; - dn = 0; - if (x > lay::wordlen) { - dn = *(ps[px1]++); - } - - uint32_t d0 = d; - if (d0 != 0) { - for (unsigned int p = 1; p <= px1; ++p) { - d |= (d0 >> p); - } - for (unsigned int p = 1; p <= px2; ++p) { - d |= (d0 << p); - } - } - if (dn != 0) { - for (unsigned int p = 1; p <= px1; ++p) { - d |= (dn << (32 - p)); - } - } - if (dd != 0) { - for (unsigned int p = 1; p <= px2; ++p) { - d |= (dd >> (32 - p)); - } - } - for (unsigned int p = 0; p < px1; ++p) { - d |= *(ps[p]++); - } - for (unsigned int p = px1 + 1; p < pixels; ++p) { - d |= *(ps[p]++); - } - - dd = d0; - - *data++ = d & *dm++; - if (dm == dp + ds) { - dm = dp; - } - - if (x > lay::wordlen) { - x -= lay::wordlen; + unsigned int bpx1 = 0, bpx2 = 0; + if (o >= px1 - spx1 && o <= px1 + spx2) { + bpx1 = px1; + bpx2 = px2; } else { - break; + bpx1 = spx1; + bpx2 = spx2; + } + + if (bpx1 > 0 || bpx2 > 0) { + + uint32_t d, dd = 0, dn; + dn = *(ps[o]++); + + unsigned int x = w; + while (true) { + + d = dn; + + dn = 0; + if (x > lay::wordlen) { + dn = *(ps[o]++); + } + + uint32_t d0 = d; + if (d0 != 0) { + for (unsigned int p = 1; p <= bpx1; ++p) { + d |= (d0 >> p); + } + for (unsigned int p = 1; p <= bpx2; ++p) { + d |= (d0 << p); + } + } + if (dn != 0) { + for (unsigned int p = 1; p <= bpx1; ++p) { + d |= (dn << (32 - p)); + } + } + if (dd != 0) { + for (unsigned int p = 1; p <= bpx2; ++p) { + d |= (dd >> (32 - p)); + } + } + + dd = d0; + + *dpp++ |= d & *dm++; + if (dm == dp + ds) { + dm = dp; + } + + if (x > lay::wordlen) { + x -= lay::wordlen; + } else { + break; + } + + } + + } else { + + unsigned int x = w; + while (true) { + + uint32_t d = *(ps[o]++); + + *dpp++ |= d & *dm++; + if (dm == dp + ds) { + dm = dp; + } + + if (x > lay::wordlen) { + x -= lay::wordlen; + } else { + break; + } + + } + } } diff --git a/src/laybasic/laybasic/layLayoutViewBase.cc b/src/laybasic/laybasic/layLayoutViewBase.cc index dc176a9e5..b6051a571 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.cc +++ b/src/laybasic/laybasic/layLayoutViewBase.cc @@ -1692,7 +1692,7 @@ LayoutViewBase::icon_for_layer (const LayerPropertiesConstIterator &iter, unsign // create text single_bitmap_to_image (lay::ViewOp (frame_color, mode, 0, 0, 0), text, &image, dither_pattern (), line_styles (), dpr_drawing, w, h); // create vertex - single_bitmap_to_image (lay::ViewOp (frame_color, mode, 0, 0, 0, lay::ViewOp::Cross, iter->marked (true) ? 9/*mark size*/ : 0), vertex, &image, dither_pattern (), line_styles (), dpr_drawing, w, h); + single_bitmap_to_image (lay::ViewOp (frame_color, mode, 0, 0, 0, lay::ViewOp::Cross, iter->marked (true) ? int (9 * dpr_drawing + 0.5) : 0), vertex, &image, dither_pattern (), line_styles (), dpr_drawing, w, h); if (oversampling > 1) { tl::PixelBuffer subsampled (image.width () / oversampling, image.height () / oversampling); From 2c96cc2a411f4e3687b16e5bb3c88f90852b141e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 2 Oct 2022 19:33:21 +0200 Subject: [PATCH 26/54] Salt package installation/uninstallation even and hooks. --- src/doc/doc/about/packages.xml | 23 +++++++++++++++++++ src/lay/lay/gsiDeclLayApplication.cc | 9 ++++++++ src/lay/lay/layApplication.cc | 10 +++++++++ src/lay/lay/layApplication.h | 10 ++++++++- src/lay/lay/laySalt.cc | 33 +++++++++++++++++++++++++++- src/lay/lay/laySaltController.cc | 13 ++++++++--- src/lay/lay/laySaltController.h | 11 ++++++++++ 7 files changed, 104 insertions(+), 5 deletions(-) diff --git a/src/doc/doc/about/packages.xml b/src/doc/doc/about/packages.xml index 2b9c3daec..a2b7dad60 100644 --- a/src/doc/doc/about/packages.xml +++ b/src/doc/doc/about/packages.xml @@ -209,4 +209,27 @@ subversion equivalent.

    +

    Installation Hooks

    + +

    + Scripts can register an event through which + indicates that packages have been installed or uninstalled. +

    + +

    + Packages itself can supply special scripts which are executed after a package was installed + or before a package is uninstalled: +

    + +
      +
    • _install.lym: if present, this script is executed after the package is installed.
    • +
    • _uninstall.lym: if present, this script is executed before the package is uninstalled.
    • +
    + +

    + Both scripts need to be stored in the same location as "grain.xml" and have to use + "lym" format. This is the generic XML script format KLayout employs as an interpreter-agnostic + script representation. +

    + diff --git a/src/lay/lay/gsiDeclLayApplication.cc b/src/lay/lay/gsiDeclLayApplication.cc index fbf23517b..f59e77244 100644 --- a/src/lay/lay/gsiDeclLayApplication.cc +++ b/src/lay/lay/gsiDeclLayApplication.cc @@ -24,6 +24,7 @@ #include "layMainWindow.h" #include "laySignalHandler.h" #include "gsiDecl.h" +#include "gsiSignals.h" #include "tlArch.h" #if defined(HAVE_QTBINDINGS) @@ -243,6 +244,14 @@ static gsi::Methods application_methods () "\n" "There is exactly one instance of the application. This instance can be obtained with this " "method." + ) + + event ("on_salt_changed", &C::salt_changed_event, + "@brief This event is triggered when the package status changes.\n" + "\n" + "Register to this event if you are interested in package changes - i.e. installation or removal of packages or " + "package updates.\n" + "\n" + "This event has been introduced in version 0.28." ) ; } diff --git a/src/lay/lay/layApplication.cc b/src/lay/lay/layApplication.cc index 5aee10b73..707416e5a 100644 --- a/src/lay/lay/layApplication.cc +++ b/src/lay/lay/layApplication.cc @@ -672,6 +672,8 @@ ApplicationBase::init_app () } } + sc->salt_changed_event.add (this, &ApplicationBase::salt_changed); + } if (tc) { @@ -852,6 +854,14 @@ ApplicationBase::add_macro_category (const std::string &name, const std::string } } +void +ApplicationBase::salt_changed () +{ +BEGIN_PROTECTED_SILENT + salt_changed_event (); +END_PROTECTED_SILENT +} + ApplicationBase::~ApplicationBase () { tl::set_ui_exception_handlers (0, 0, 0); diff --git a/src/lay/lay/layApplication.h b/src/lay/lay/layApplication.h index 6b42e1318..79cf507fb 100644 --- a/src/lay/lay/layApplication.h +++ b/src/lay/lay/layApplication.h @@ -26,6 +26,7 @@ #include "layCommon.h" #include "layBusy.h" +#include "tlEvents.h" #include #include @@ -76,7 +77,7 @@ class LayoutView; * and one for the GUI version (derived from QApplication). */ class LAY_PUBLIC ApplicationBase - : public gsi::ObjectBase + : public gsi::ObjectBase, public tl::Object { public: /** @@ -313,6 +314,11 @@ public: */ void init_app (); + /** + * @brief An event indicating that the package collection has changed + */ + tl::Event salt_changed_event; + /** * @brief Gets the QApplication object * This method will return non-null only if a GUI-enabled application is present. @@ -376,6 +382,8 @@ private: // in order to maintain a valid MainWindow reference for ruby scripts and Ruby's GC all the time. gsi::Interpreter *mp_ruby_interpreter; gsi::Interpreter *mp_python_interpreter; + + void salt_changed (); }; /** diff --git a/src/lay/lay/laySalt.cc b/src/lay/lay/laySalt.cc index 6c47c949b..45313074d 100644 --- a/src/lay/lay/laySalt.cc +++ b/src/lay/lay/laySalt.cc @@ -26,6 +26,7 @@ #include "tlLog.h" #include "tlInternational.h" #include "tlWebDAV.h" +#include "lymMacro.h" #include #include @@ -281,6 +282,21 @@ Salt::remove_grain (const SaltGrain &grain) QString name = tl::to_qstring (grain.name ()); tl::info << QObject::tr ("Removing package '%1' ..").arg (name); + + // Execute "_uninstall.lym" if it exists + try { + QFile uninstall_lym (QDir (tl::to_qstring (grain.path ())).absoluteFilePath (tl::to_qstring ("_uninstall.lym"))); + if (uninstall_lym.exists ()) { + lym::Macro uninstall; + uninstall.load_from (tl::to_string (uninstall_lym.fileName ())); + uninstall.set_file_path (tl::to_string (uninstall_lym.fileName ())); + uninstall.run (); + } + } catch (tl::Exception &ex) { + // Errors in the uninstallation script are only logged, but do not prevent uninstallation + tl::error << ex.msg (); + } + bool res = remove_from_collection (m_root, grain.name ()); if (res) { tl::info << QObject::tr ("Package '%1' removed.").arg (name); @@ -493,10 +509,25 @@ Salt::create_grain (const SaltGrain &templ, SaltGrain &target, double timeout, t if (res) { - tl::info << QObject::tr ("Package '%1' installed").arg (tl::to_qstring (target.name ())); target.set_installed_time (QDateTime::currentDateTime ()); target.save (); + // Execute "_install.lym" if it exists + try { + QFile install_lym (QDir (tl::to_qstring (target.path ())).absoluteFilePath (tl::to_qstring ("_install.lym"))); + if (install_lym.exists ()) { + lym::Macro install; + install.load_from (tl::to_string (install_lym.fileName ())); + install.set_file_path (tl::to_string (install_lym.fileName ())); + install.run (); + } + } catch (tl::Exception &ex) { + // Errors in the installation script are only logged, but do not prevent installation + tl::error << ex.msg (); + } + + tl::info << QObject::tr ("Package '%1' installed").arg (tl::to_qstring (target.name ())); + // NOTE: this is a bit brute force .. we could as well try to insert the new grain into the existing structure refresh (); diff --git a/src/lay/lay/laySaltController.cc b/src/lay/lay/laySaltController.cc index bfc5af1b2..17967733e 100644 --- a/src/lay/lay/laySaltController.cc +++ b/src/lay/lay/laySaltController.cc @@ -58,13 +58,13 @@ SaltController::initialized (lay::Dispatcher * /*root*/) connect (m_file_watcher, SIGNAL (fileRemoved (const QString &)), this, SLOT (file_watcher_triggered ())); } - connect (&m_salt, SIGNAL (collections_changed ()), this, SIGNAL (salt_changed ())); + connect (&m_salt, SIGNAL (collections_changed ()), this, SLOT (emit_salt_changed ())); } void SaltController::uninitialize (lay::Dispatcher * /*root*/) { - disconnect (&m_salt, SIGNAL (collections_changed ()), this, SIGNAL (salt_changed ())); + disconnect (&m_salt, SIGNAL (collections_changed ()), this, SLOT (emit_salt_changed ())); if (m_file_watcher) { disconnect (m_file_watcher, SIGNAL (fileChanged (const QString &)), this, SLOT (file_watcher_triggered ())); @@ -168,7 +168,7 @@ void SaltController::sync_files () { tl::log << tl::to_string (tr ("Detected file system change in packages - updating")); - emit salt_changed (); + emit_salt_changed (); } bool @@ -242,6 +242,13 @@ SaltController::file_watcher_triggered () dm_sync_files (); } +void +SaltController::emit_salt_changed () +{ + salt_changed_event (); + emit salt_changed (); +} + void SaltController::set_salt_mine_url (const std::string &url) { diff --git a/src/lay/lay/laySaltController.h b/src/lay/lay/laySaltController.h index f660039a6..e2a9fa0fb 100644 --- a/src/lay/lay/laySaltController.h +++ b/src/lay/lay/laySaltController.h @@ -29,6 +29,7 @@ #include "laySalt.h" #include "tlFileSystemWatcher.h" #include "tlDeferredExecution.h" +#include "tlEvents.h" #include #include @@ -171,6 +172,11 @@ public: return m_salt; } + /** + * @brief Event-style version of "salt_changed" + */ + tl::Event salt_changed_event; + /** * @brief Gets the singleton instance for this object */ @@ -182,6 +188,11 @@ private slots: */ void file_watcher_triggered (); + /** + * @brief Emits a salt_changed event + signal + */ + void emit_salt_changed (); + signals: /** * @brief This signal is emitted if the salt changed From 0c73b11f9b21d55f30ed47c00705c1a70e72d77a Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 3 Oct 2022 22:02:13 +0200 Subject: [PATCH 27/54] Added a convenience version for GenericDeviceExtractor#define_terminal which takes terminal and layer names --- src/db/db/gsiDeclDbNetlistDeviceExtractor.cc | 45 ++++++++++++++++++++ testdata/lvs/res_combine3.lvs | 4 +- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/db/db/gsiDeclDbNetlistDeviceExtractor.cc b/src/db/db/gsiDeclDbNetlistDeviceExtractor.cc index 2bf9b60d2..bb5d379eb 100644 --- a/src/db/db/gsiDeclDbNetlistDeviceExtractor.cc +++ b/src/db/db/gsiDeclDbNetlistDeviceExtractor.cc @@ -295,6 +295,30 @@ Class decl_dbNetlistDeviceExtractor ("db", "DeviceEx "This class has been introduced in version 0.26." ); +template +static void +define_terminal_by_names (GenericDeviceExtractor *extractor, db::Device *device, const std::string &terminal_name, const std::string &layer_name, const Shape &shape) +{ + if (! extractor->device_class ()) { + throw tl::Exception (tl::to_string (tr ("No device class registered yet"))); + } + + size_t terminal_id = extractor->device_class ()->terminal_id_for_name (terminal_name); + + size_t layer_id = std::numeric_limits::max (); + for (auto l = extractor->begin_layer_definitions (); l != extractor->end_layer_definitions (); ++l) { + if (l->name == layer_name) { + layer_id = l->index; + } + } + + if (layer_id == std::numeric_limits::max ()) { + throw tl::Exception (tl::to_string (tr ("Not a valid layer name: ")) + layer_name); + } + + extractor->define_terminal (device, terminal_id, layer_id, shape); +} + Class decl_GenericDeviceExtractor (decl_dbNetlistDeviceExtractor, "db", "GenericDeviceExtractor", gsi::callback ("setup", &GenericDeviceExtractor::setup, &GenericDeviceExtractor::cb_setup, "@brief Sets up the extractor.\n" @@ -392,6 +416,27 @@ Class decl_GenericDeviceExtractor (decl_dbNetlistDeviceE "This version produces a point-like terminal. Note that the point is\n" "specified in database units.\n" ) + + gsi::method_ext ("define_terminal", &define_terminal_by_names, + gsi::arg ("device"), gsi::arg ("terminal_name"), gsi::arg ("layer_name"), gsi::arg ("shape"), + "@brief Defines a device terminal using names for terminal and layer.\n" + "\n" + "This convenience version of the ID-based \\define_terminal methods allows using names for terminal and layer.\n" + "It has been introduced in version 0.28." + ) + + gsi::method_ext ("define_terminal", &define_terminal_by_names, + gsi::arg ("device"), gsi::arg ("terminal_name"), gsi::arg ("layer_name"), gsi::arg ("shape"), + "@brief Defines a device terminal using names for terminal and layer.\n" + "\n" + "This convenience version of the ID-based \\define_terminal methods allows using names for terminal and layer.\n" + "It has been introduced in version 0.28." + ) + + gsi::method_ext ("define_terminal", &define_terminal_by_names, + gsi::arg ("device"), gsi::arg ("terminal_name"), gsi::arg ("layer_name"), gsi::arg ("point"), + "@brief Defines a device terminal using names for terminal and layer.\n" + "\n" + "This convenience version of the ID-based \\define_terminal methods allows using names for terminal and layer.\n" + "It has been introduced in version 0.28." + ) + gsi::method ("dbu", &GenericDeviceExtractor::dbu, "@brief Gets the database unit\n" ) + diff --git a/testdata/lvs/res_combine3.lvs b/testdata/lvs/res_combine3.lvs index e0ebec331..c609c0824 100644 --- a/testdata/lvs/res_combine3.lvs +++ b/testdata/lvs/res_combine3.lvs @@ -67,8 +67,8 @@ class ResistorExtractor < RBA::GenericDeviceExtractor device.set_parameter(RBA::DeviceClassResistor::PARAM_L, l) device.set_parameter(RBA::DeviceClassResistor::PARAM_P, 2*l+2*w) device.set_parameter(RBA::DeviceClassResistor::PARAM_W, w) - define_terminal(device, RBA::DeviceClassResistor::TERMINAL_A, 0, terminals[0]); - define_terminal(device, RBA::DeviceClassResistor::TERMINAL_B, 0, terminals[1]); + define_terminal(device, "A", "C", terminals[0]); + define_terminal(device, "B", "C", terminals[1]); end end end From 774b00841705e169e681dc0138e1e9fed042d871 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 5 Oct 2022 21:45:22 +0200 Subject: [PATCH 28/54] Fixed a build error --- .../net_tracer/lay_plugin/layNetTracerTechComponentEditor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.h b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.h index 475b81e84..df7a60cc7 100644 --- a/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.h +++ b/src/plugins/tools/net_tracer/lay_plugin/layNetTracerTechComponentEditor.h @@ -67,7 +67,7 @@ public slots: void move_up_clicked (); void move_down_clicked (); void current_item_changed (QTreeWidgetItem *current, QTreeWidgetItem *previous); - void item_changed (QTreeWidgetItem *item, int column); + private: db::NetTracerTechnologyComponent m_data; From ddff1f564f8273b40acfe2837401244e9a4783bd Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 5 Oct 2022 23:50:43 +0200 Subject: [PATCH 29/54] Provide high-res version of logo --- src/icons/icons.qrc | 1 + src/icons/images/logo.png | Bin 73051 -> 58289 bytes src/icons/images/logo@2x.png | Bin 0 -> 177262 bytes src/lay/lay/layViewWidgetStack.cc | 8 +++++++- 4 files changed, 8 insertions(+), 1 deletion(-) create mode 100755 src/icons/images/logo@2x.png diff --git a/src/icons/icons.qrc b/src/icons/icons.qrc index 79bdffc17..a855b907b 100644 --- a/src/icons/icons.qrc +++ b/src/icons/icons.qrc @@ -117,6 +117,7 @@ images/left_16px.png images/left_16px@2x.png images/logo.png + images/logo@2x.png images/lt_31px.png images/lt_31px@2x.png images/m0_24px.png diff --git a/src/icons/images/logo.png b/src/icons/images/logo.png index 4451152f6422fdc159579036cb43c756534a3228..422deaeb61c2cfdbe1454454a0ffe11825e2b21b 100644 GIT binary patch literal 58289 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
    FX^HCPx`2457bV?bgVI|jU>qUQBAg)9SAV@ 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 diff --git a/src/icons/images/logo@2x.png b/src/icons/images/logo@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..d2f6cbec7c46c88f9adf432608ad95711967a57d GIT binary patch literal 177262 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%0Awlvg4j)!UjZbi9&er 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 (true); - mp_bglabel->setText (QObject::tr ("

    Use File/Open to open a layout

    ")); + std::string logo = "logo.png"; +#if QT_VERSION >= 0x50000 + if (devicePixelRatio () >= 2.0) { + logo = "logo@2x.png"; + } +#endif + mp_bglabel->setText (QObject::tr ("

    Use File/Open to open a layout

    ").arg (tl::to_qstring (logo))); mp_bglabel->setAlignment (Qt::AlignVCenter | Qt::AlignHCenter); mp_bglabel->show (); } From eb004e45e10fb88e5a9ae6bdb32d34b690901375 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 6 Oct 2022 21:31:16 +0200 Subject: [PATCH 30/54] WIP: generalized interface of properties page --- src/ant/ant/antPropertiesPage.cc | 45 +++++--------- src/ant/ant/antPropertiesPage.h | 11 ++-- src/edt/edt/edtInstPropertiesPage.cc | 37 +++-------- src/edt/edt/edtInstPropertiesPage.h | 9 +-- src/edt/edt/edtPropertiesPages.cc | 37 +++-------- src/edt/edt/edtPropertiesPages.h | 9 +-- src/img/img/imgPropertiesPage.cc | 54 +++++----------- src/img/img/imgPropertiesPage.h | 11 ++-- src/laybasic/laybasic/layProperties.h | 86 ++++++-------------------- src/layui/layui/layPropertiesDialog.cc | 52 +++++++++------- src/layui/layui/layPropertiesDialog.h | 2 +- 11 files changed, 117 insertions(+), 236 deletions(-) diff --git a/src/ant/ant/antPropertiesPage.cc b/src/ant/ant/antPropertiesPage.cc index 7e55c2afc..6af023f14 100644 --- a/src/ant/ant/antPropertiesPage.cc +++ b/src/ant/ant/antPropertiesPage.cc @@ -65,7 +65,7 @@ PropertiesPage::PropertiesPage (ant::Service *rulers, db::Manager *manager, QWid : lay::PropertiesPage (parent, manager, rulers), mp_rulers (rulers), m_enable_cb_callback (true), m_in_something_changed (false) { mp_rulers->get_selection (m_selection); - m_pos = m_selection.begin (); + m_index = 0; setupUi (this); @@ -126,18 +126,6 @@ PropertiesPage::~PropertiesPage () mp_rulers->restore_highlights (); } -void -PropertiesPage::back () -{ - m_pos = m_selection.end (); -} - -void -PropertiesPage::front () -{ - m_pos = m_selection.begin (); -} - void PropertiesPage::swap_points_clicked () { @@ -374,32 +362,27 @@ PropertiesPage::snap_to_layout_clicked () const ant::Object & PropertiesPage::current () const { - const ant::Object *ruler = dynamic_cast ((*m_pos)->ptr ()); + const ant::Object *ruler = dynamic_cast (m_selection [m_index]->ptr ()); return *ruler; } -bool -PropertiesPage::at_begin () const +size_t +PropertiesPage::count () const { - return (m_pos == m_selection.begin ()); + return m_selection.size (); } -bool -PropertiesPage::at_end () const +void +PropertiesPage::select_entries (const std::vector &entries) { - return (m_pos == m_selection.end ()); + tl_assert (entries.size () == 1); // @@@ + m_index = entries.front (); } -void -PropertiesPage::operator-- () +std::string +PropertiesPage::description (size_t entry) const { - --m_pos; -} - -void -PropertiesPage::operator++ () -{ - ++m_pos; + return "Ruler"; // @@@ } void @@ -411,7 +394,7 @@ PropertiesPage::leave () void PropertiesPage::update () { - mp_rulers->highlight (std::distance (m_selection.begin (), m_pos)); + mp_rulers->highlight (m_index); update_with (current ()); } @@ -504,7 +487,7 @@ PropertiesPage::apply () { ant::Object obj; get_object (obj); - mp_rulers->change_ruler (*m_pos, obj); + mp_rulers->change_ruler (m_selection [m_index], obj); // @@@ multi-apply } void PropertiesPage::get_object(ant::Object &obj) diff --git a/src/ant/ant/antPropertiesPage.h b/src/ant/ant/antPropertiesPage.h index 1f1ead4da..ce90940f0 100644 --- a/src/ant/ant/antPropertiesPage.h +++ b/src/ant/ant/antPropertiesPage.h @@ -43,12 +43,9 @@ public: PropertiesPage (ant::Service *rulers, db::Manager *manager, QWidget *parent); ~PropertiesPage (); - virtual void back (); - virtual void front (); - virtual bool at_begin () const; - virtual bool at_end () const; - virtual void operator-- (); - virtual void operator++ (); + virtual size_t count () const; + virtual void select_entries (const std::vector &entries); + virtual std::string description (size_t entry) const; virtual void update (); virtual void leave (); virtual bool readonly (); @@ -61,7 +58,7 @@ private slots: private: std::vector m_selection; - std::vector ::iterator m_pos; + size_t m_index; ant::Service *mp_rulers; bool m_enable_cb_callback; bool m_in_something_changed; diff --git a/src/edt/edt/edtInstPropertiesPage.cc b/src/edt/edt/edtInstPropertiesPage.cc index cde690911..abd87b16a 100644 --- a/src/edt/edt/edtInstPropertiesPage.cc +++ b/src/edt/edt/edtInstPropertiesPage.cc @@ -226,40 +226,23 @@ InstPropertiesPage::display_mode_changed (bool) update (); } -void -InstPropertiesPage::back () +size_t +InstPropertiesPage::count () const { - m_index = (unsigned int) m_selection_ptrs.size (); + return m_selection_ptrs.size (); } -void -InstPropertiesPage::front () +void +InstPropertiesPage::select_entries (const std::vector &entries) { - m_index = 0; + tl_assert (entries.size () == 1); // @@@ + m_index = entries.front (); } -bool -InstPropertiesPage::at_begin () const +std::string +InstPropertiesPage::description (size_t entry) const { - return (m_index == 0); -} - -bool -InstPropertiesPage::at_end () const -{ - return (m_index == m_selection_ptrs.size ()); -} - -void -InstPropertiesPage::operator-- () -{ - --m_index; -} - -void -InstPropertiesPage::operator++ () -{ - ++m_index; + return m_selection_ptrs [entry]->back ().inst_ptr.to_string (); // @@@ } void diff --git a/src/edt/edt/edtInstPropertiesPage.h b/src/edt/edt/edtInstPropertiesPage.h index 07a193a76..603c0f9da 100644 --- a/src/edt/edt/edtInstPropertiesPage.h +++ b/src/edt/edt/edtInstPropertiesPage.h @@ -47,12 +47,9 @@ public: InstPropertiesPage (edt::Service *service, db::Manager *manager, QWidget *parent); ~InstPropertiesPage (); - virtual void back (); - virtual void front (); - virtual bool at_begin () const; - virtual bool at_end () const; - virtual void operator-- (); - virtual void operator++ (); + virtual size_t count () const; + virtual void select_entries (const std::vector &entries); + virtual std::string description (size_t entry) const; virtual void leave (); private: diff --git a/src/edt/edt/edtPropertiesPages.cc b/src/edt/edt/edtPropertiesPages.cc index 7f01f3158..a89f07b23 100644 --- a/src/edt/edt/edtPropertiesPages.cc +++ b/src/edt/edt/edtPropertiesPages.cc @@ -73,40 +73,23 @@ ShapePropertiesPage::setup () m_enable_cb_callback = true; } -void -ShapePropertiesPage::back () +size_t +ShapePropertiesPage::count () const { - m_index = (unsigned int) m_selection_ptrs.size (); + return m_selection_ptrs.size (); } -void -ShapePropertiesPage::front () +void +ShapePropertiesPage::select_entries (const std::vector &entries) { - m_index = 0; + tl_assert (entries.size () == 1); // @@@ + m_index = entries.front (); } -bool -ShapePropertiesPage::at_begin () const +std::string +ShapePropertiesPage::description (size_t entry) const { - return (m_index == 0); -} - -bool -ShapePropertiesPage::at_end () const -{ - return (m_index == m_selection_ptrs.size ()); -} - -void -ShapePropertiesPage::operator-- () -{ - --m_index; -} - -void -ShapePropertiesPage::operator++ () -{ - ++m_index; + return m_selection_ptrs [entry]->shape ().to_string (); // @@@ } void diff --git a/src/edt/edt/edtPropertiesPages.h b/src/edt/edt/edtPropertiesPages.h index 0172cd1c5..03c156fb2 100644 --- a/src/edt/edt/edtPropertiesPages.h +++ b/src/edt/edt/edtPropertiesPages.h @@ -49,12 +49,9 @@ public: ShapePropertiesPage (edt::Service *service, db::Manager *manager, QWidget *parent); ~ShapePropertiesPage (); - virtual void back (); - virtual void front (); - virtual bool at_begin () const; - virtual bool at_end () const; - virtual void operator-- (); - virtual void operator++ (); + virtual size_t count () const; + virtual void select_entries (const std::vector &entries); + virtual std::string description (size_t entry) const; virtual void leave (); protected: diff --git a/src/img/img/imgPropertiesPage.cc b/src/img/img/imgPropertiesPage.cc index 7107a0a90..09b11ed16 100644 --- a/src/img/img/imgPropertiesPage.cc +++ b/src/img/img/imgPropertiesPage.cc @@ -44,7 +44,7 @@ PropertiesPage::PropertiesPage (img::Service *service, db::Manager *manager, QWi : lay::PropertiesPage (parent, manager, service), mp_service (service), mp_direct_image (0) { mp_service->get_selection (m_selection); - m_pos = m_selection.begin (); + m_index = 0; mp_service->clear_highlights (); @@ -154,44 +154,24 @@ PropertiesPage::invalidate () } } -void -PropertiesPage::back () +size_t +PropertiesPage::count () const { - m_pos = m_selection.end (); + return m_selection.size (); +} + +void +PropertiesPage::select_entries (const std::vector &entries) +{ + tl_assert (entries.size () == 1); // @@@ + m_index = entries.front (); invalidate (); } -void -PropertiesPage::front () +std::string +PropertiesPage::description (size_t entry) const { - m_pos = m_selection.begin (); - invalidate (); -} - -bool -PropertiesPage::at_begin () const -{ - return (m_pos == m_selection.begin ()); -} - -bool -PropertiesPage::at_end () const -{ - return (m_pos == m_selection.end ()); -} - -void -PropertiesPage::operator-- () -{ - --m_pos; - invalidate (); -} - -void -PropertiesPage::operator++ () -{ - ++m_pos; - invalidate (); + return "image"; // @@@ } void @@ -378,11 +358,11 @@ PropertiesPage::update () if (mp_service) { - mp_service->highlight (std::distance (m_selection.begin (), m_pos)); + mp_service->highlight (m_index); // create a local copy in which we can apply modifications if (! mp_direct_image) { - const img::Object *image = dynamic_cast ((*m_pos)->ptr ()); + const img::Object *image = dynamic_cast (m_selection [m_index]->ptr ()); mp_direct_image = new img::Object (*image); } @@ -908,7 +888,7 @@ PropertiesPage::apply () mp_direct_image->set_data_mapping (dm); if (mp_service) { - mp_service->change_image (*m_pos, *mp_direct_image); + mp_service->change_image (m_selection [m_index], *mp_direct_image); } } diff --git a/src/img/img/imgPropertiesPage.h b/src/img/img/imgPropertiesPage.h index 797bedd86..4659009ab 100644 --- a/src/img/img/imgPropertiesPage.h +++ b/src/img/img/imgPropertiesPage.h @@ -50,12 +50,9 @@ public: PropertiesPage (QWidget *parent); ~PropertiesPage (); - virtual void back (); - virtual void front (); - virtual bool at_begin () const; - virtual bool at_end () const; - virtual void operator-- (); - virtual void operator++ (); + virtual size_t count () const; + virtual void select_entries (const std::vector &entries); + virtual std::string description (size_t entry) const; virtual void update (); virtual void leave (); virtual bool readonly (); @@ -91,7 +88,7 @@ private slots: private: std::vector m_selection; - std::vector ::iterator m_pos; + size_t m_index; img::Service *mp_service; img::Object *mp_direct_image; bool m_no_signals; diff --git a/src/laybasic/laybasic/layProperties.h b/src/laybasic/laybasic/layProperties.h index 07c6ffe98..605489b12 100644 --- a/src/laybasic/laybasic/layProperties.h +++ b/src/laybasic/laybasic/layProperties.h @@ -69,80 +69,33 @@ public: virtual ~PropertiesPage (); /** - * @brief Move the current pointer to the end of the selected objects list - * - * Must be implemented by the Editable function and must set the - * selected object pointer to past-the-end of the list. at_end () must - * return true after this. + * @brief Gets the number of entries represented by this page */ - virtual void back () = 0; + virtual size_t count () const = 0; /** - * @brief A helper function for the dialog that additionally reports - * if there are any elements in the selection + * @brief Selects the entries with the given indexes + * + * If multiple indexes are selected, the properties page shows + * all items with different values as "leave as is". Items with + * same value are shown with the given value. */ - bool back_checked () + virtual void select_entries (const std::vector &entries) = 0; + + /** + * @brief Convenience function to select a specific entry + */ + void select_entry (size_t entry) { - back (); - return ! at_begin (); + std::vector entries; + entries.push_back (entry); + select_entries (entries); } /** - * @brief Move the current pointer to the beginning of the selected objects list - * - * Must be implemented by the Editable function and must set the - * selected object pointer to the beginning of the list. at_begin () must - * return true after this. - * This method must return true, if there are any elements in the selection - * - i.e. operator++ () will be successful afterwards. + * @brief Gets a description text for the nth entry */ - virtual void front () = 0; - - /** - * @brief A helper function for the dialog that additionally reports - * if there are any elements in the selection - */ - bool front_checked () - { - front (); - return ! at_end (); - } - - /** - * @brief Tell if the current object references the first one - * - * Must be implemented by the Editable function and must return - * true if the current object is the first one - i.e. if an - * operator-- would render the status invalid. - * If no object is referenced, this method and at_end () must return true. - */ - virtual bool at_begin () const = 0; - - /** - * @brief Tell if the current object references past the last one - * - * If the current object pointer is past the end of the selected - * object space, - */ - virtual bool at_end () const = 0; - - /** - * @brief Step one element back - * - * This method is supposed to move the current pointer one position - * back. If at_begin () was true before, the result may be unpredictable. - * The dialog will call update () to update the display accordingly. - */ - virtual void operator-- () = 0; - - /** - * @brief Advance one element - * - * This method is supposed to move the current pointer one position - * forward. If at_end () was true before, the result may be unpredictable. - * The dialog will call update () to update the display accordingly. - */ - virtual void operator++ () = 0; + virtual std::string description (size_t entry) const = 0; /** * @brief Update the display @@ -176,7 +129,7 @@ public: /** * @brief Apply any changes to the current object * - * Apply any changes to the current object. If nothing was + * Apply any changes to the current objects. If nothing was * changed, the object may be left untouched. * The dialog will start a transaction on the manager object. */ @@ -187,6 +140,7 @@ public: /** * @brief Returns true, if the properties page supports "apply all" + * @@@ TODO: remove */ virtual bool can_apply_to_all () const { diff --git a/src/layui/layui/layPropertiesDialog.cc b/src/layui/layui/layPropertiesDialog.cc index 37e00cda9..02986a16f 100644 --- a/src/layui/layui/layPropertiesDialog.cc +++ b/src/layui/layui/layPropertiesDialog.cc @@ -38,7 +38,7 @@ namespace lay PropertiesDialog::PropertiesDialog (QWidget * /*parent*/, db::Manager *manager, lay::Editables *editables) : QDialog (0 /*parent*/), - mp_manager (manager), mp_editables (editables), m_index (-1), m_auto_applied (false), m_transaction_id (0) + mp_manager (manager), mp_editables (editables), m_index (-1), m_object_index (0), m_auto_applied (false), m_transaction_id (0) { mp_ui = new Ui::PropertiesDialog (); @@ -83,7 +83,7 @@ PropertiesDialog::PropertiesDialog (QWidget * /*parent*/, db::Manager *manager, // look for next usable editable while (m_index < int (mp_properties_pages.size ()) && - (m_index < 0 || mp_properties_pages [m_index] == 0 || mp_properties_pages [m_index]->at_end ())) { + (m_index < 0 || mp_properties_pages [m_index] == 0 || m_object_index >= int (mp_properties_pages [m_index]->count ()))) { ++m_index; } @@ -103,8 +103,9 @@ PropertiesDialog::PropertiesDialog (QWidget * /*parent*/, db::Manager *manager, } else { mp_ui->next_button->setEnabled (any_next ()); - mp_properties_pages [m_index]->update (); mp_stack->setCurrentWidget (mp_properties_pages [m_index]); + mp_properties_pages [m_index]->select_entry (m_object_index); + mp_properties_pages [m_index]->update (); mp_ui->apply_to_all_cbx->setEnabled (! mp_properties_pages [m_index]->readonly () && mp_properties_pages [m_index]->can_apply_to_all ()); mp_ui->apply_to_all_cbx->setChecked (false); mp_ui->relative_cbx->setEnabled (mp_ui->apply_to_all_cbx->isEnabled () && mp_ui->apply_to_all_cbx->isChecked ()); @@ -152,21 +153,26 @@ BEGIN_PROTECTED } // advance the current entry - ++(*mp_properties_pages [m_index]); + ++m_object_index; // look for next usable editable if at end - if (mp_properties_pages [m_index]->at_end ()) { + if (m_object_index >= int (mp_properties_pages [m_index]->count ())) { + mp_properties_pages [m_index]->leave (); ++m_index; + m_object_index = 0; + while (m_index < int (mp_properties_pages.size ()) && - (mp_properties_pages [m_index] == 0 || ! mp_properties_pages [m_index]->front_checked ())) { + (mp_properties_pages [m_index] == 0 || mp_properties_pages [m_index]->count () == 0)) { ++m_index; } // because we checked that there are any further elements, this should not happen: if (m_index >= int (mp_properties_pages.size ())) { return; } + mp_stack->setCurrentWidget (mp_properties_pages [m_index]); + } ++m_current_object; @@ -177,6 +183,7 @@ BEGIN_PROTECTED mp_ui->apply_to_all_cbx->setEnabled (! mp_properties_pages [m_index]->readonly () && mp_properties_pages [m_index]->can_apply_to_all ()); mp_ui->relative_cbx->setEnabled (mp_ui->apply_to_all_cbx->isEnabled () && mp_ui->apply_to_all_cbx->isChecked ()); mp_ui->ok_button->setEnabled (! mp_properties_pages [m_index]->readonly ()); + mp_properties_pages [m_index]->select_entry (m_object_index); mp_properties_pages [m_index]->update (); END_PROTECTED @@ -195,25 +202,28 @@ BEGIN_PROTECTED } } - if (mp_properties_pages [m_index]->at_begin ()) { + if (m_object_index == 0) { // look for last usable editable if at end mp_properties_pages [m_index]->leave (); --m_index; while (m_index >= 0 && - (mp_properties_pages [m_index] == 0 || ! mp_properties_pages [m_index]->back_checked ())) { + (mp_properties_pages [m_index] == 0 || mp_properties_pages [m_index]->count () == 0)) { --m_index; } // because we checked that there are any further elements, this should not happen: if (m_index < 0) { return; } + + m_object_index = mp_properties_pages [m_index]->count () - 1; + mp_stack->setCurrentWidget (mp_properties_pages [m_index]); - + } // decrement the current entry - --(*mp_properties_pages [m_index]); + --m_object_index; --m_current_object; update_title (); @@ -223,6 +233,7 @@ BEGIN_PROTECTED mp_ui->apply_to_all_cbx->setEnabled (! mp_properties_pages [m_index]->readonly () && mp_properties_pages [m_index]->can_apply_to_all ()); mp_ui->relative_cbx->setEnabled (mp_ui->apply_to_all_cbx->isEnabled () && mp_ui->apply_to_all_cbx->isChecked ()); mp_ui->ok_button->setEnabled (! mp_properties_pages [m_index]->readonly ()); + mp_properties_pages [m_index]->select_entry (m_object_index); mp_properties_pages [m_index]->update (); END_PROTECTED @@ -237,32 +248,31 @@ PropertiesDialog::update_title () bool PropertiesDialog::any_next () const { - // test-advance + // look for the next applicable page + // @@@ Pages should not be empty int index = m_index; - ++(*mp_properties_pages [index]); - if (mp_properties_pages [index]->at_end ()) { + if (m_object_index + 1 >= int (mp_properties_pages [index]->count ())) { ++index; - while (index < int (mp_properties_pages.size ()) && - (mp_properties_pages [index] == 0 || ! mp_properties_pages [index]->front_checked ())) { + while (index < int (mp_properties_pages.size ()) && + (mp_properties_pages [index] == 0 || mp_properties_pages [index]->count () == 0)) { ++index; } } // return true, if not at end - bool ret = (index < int (mp_properties_pages.size ())); - --(*mp_properties_pages [m_index]); - return ret; + return (index < int (mp_properties_pages.size ())); } bool PropertiesDialog::any_prev () const { - // test-decrement + // look for the next applicable page + // @@@ Pages should not be empty int index = m_index; - if (mp_properties_pages [index]->at_begin ()) { + if (m_object_index == 0) { --index; while (index >= 0 && - (mp_properties_pages [index] == 0 || ! mp_properties_pages [index]->back_checked ())) { + (mp_properties_pages [index] == 0 || mp_properties_pages [index]->count () == 0)) { --index; } } diff --git a/src/layui/layui/layPropertiesDialog.h b/src/layui/layui/layPropertiesDialog.h index 94fab9db2..583141a25 100644 --- a/src/layui/layui/layPropertiesDialog.h +++ b/src/layui/layui/layPropertiesDialog.h @@ -82,7 +82,7 @@ private: std::vector mp_properties_pages; db::Manager *mp_manager; lay::Editables *mp_editables; - int m_index; + int m_index, m_object_index; QStackedLayout *mp_stack; lay::MainWindow *mp_mw; size_t m_objects, m_current_object; From 56bdf3b2e03158d83b9916ddcbdacbad0c9d95c7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 7 Oct 2022 00:43:52 +0200 Subject: [PATCH 31/54] WIP: Fixed colors of logo letters --- src/icons/images/logo.png | Bin 58289 -> 58737 bytes src/icons/images/logo@2x.png | Bin 177262 -> 178683 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/icons/images/logo.png b/src/icons/images/logo.png index 422deaeb61c2cfdbe1454454a0ffe11825e2b21b..2095cab0bac24547df76dc38789eec8a1cda8345 100644 GIT binary patch delta 58186 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
    er5>+hnM4T9w!HQh$WpP3 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