/* KLayout Layout Viewer Copyright (C) 2006-2016 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 "layLayoutStatisticsForm.h" #include "layLayoutView.h" #include "tlInternational.h" #include "tlString.h" #include "tlExpression.h" #include "tlTimer.h" #include "dbLayoutQuery.h" #include #include #include #include #include #include #include #include #if QT_VERSION >= 0x050000 # include #endif namespace lay { /** * @brief A comparison operator to sort layers by layer and datatype and then by name */ struct CompareLDName { CompareLDName (const db::Layout &l) : mp_layout (&l) { // .. nothing yet .. } bool operator() (unsigned int a, unsigned int b) { if (mp_layout->is_valid_layer (a) && mp_layout->is_valid_layer (b)) { const db::LayerProperties &lp_a = mp_layout->get_properties (a); const db::LayerProperties &lp_b = mp_layout->get_properties (b); if (lp_a.layer != lp_b.layer) { return lp_a.layer < lp_b.layer; } if (lp_a.datatype != lp_b.datatype) { return lp_a.datatype < lp_b.datatype; } if (lp_a.name != lp_b.name) { return lp_a.name < lp_b.name; } } return false; } private: const db::Layout *mp_layout; }; /** * @brief A comparison operator to sort layers by name and then by layer and datatype */ struct CompareNameLD { CompareNameLD (const db::Layout &l) : mp_layout (&l) { // .. nothing yet .. } bool operator() (unsigned int a, unsigned int b) { if (mp_layout->is_valid_layer (a) && mp_layout->is_valid_layer (b)) { const db::LayerProperties &lp_a = mp_layout->get_properties (a); const db::LayerProperties &lp_b = mp_layout->get_properties (b); if (lp_a.name != lp_b.name) { return lp_a.name < lp_b.name; } if (lp_a.layer != lp_b.layer) { return lp_a.layer < lp_b.layer; } if (lp_a.datatype != lp_b.datatype) { return lp_a.datatype < lp_b.datatype; } } return false; } private: const db::Layout *mp_layout; }; static std::string format_tech_name (const std::string &s) { if (s.empty ()) { return s; } else { return " ('" + s + "')"; } } // ------------------------------------------------------------ /** * @brief A template processor for creating HTML pages from a template * * TODO: this is just a first step and far from being complete. * The template processor is used from the browser page by using an extension .stxml. * It reads a XML file from a resource path ":/st/" and converts it into HTML. */ class StatisticsTemplateProcessor { public: StatisticsTemplateProcessor (const QUrl &url, const db::Layout *layout) : mp_layout (layout) { QResource res (QString::fromUtf8 (":/st/") + url.path ()); if (res.isCompressed ()) { m_temp = qUncompress ((const unsigned char *)res.data (), (int)res.size ()); } else { m_temp = QByteArray ((const char *)res.data (), (int)res.size ()); } #if QT_VERSION >= 0x050000 QList > queryItems = QUrlQuery (url.query ()).queryItems (); #else QList > queryItems = url.queryItems (); #endif for (QList >::const_iterator q = queryItems.begin (); q != queryItems.end (); ++q) { m_top_eval.set_var (tl::to_string (q->first), tl::to_string (q->second)); } } bool process (); const QByteArray &get () const { return m_output.buffer (); } private: QByteArray m_temp; QBuffer m_output; tl::Eval m_top_eval; const db::Layout *mp_layout; void process (const QDomElement &element, tl::Eval &eval, QXmlStreamWriter &writer); void process_child_nodes (const QDomElement &element, tl::Eval &eval, QXmlStreamWriter &writer); }; bool StatisticsTemplateProcessor::process () { m_output.open (QIODevice::WriteOnly); bool ret_value = false; try { tl::SelfTimer timer (tl::verbosity () > 21, "StatisticsForm: create content"); QDomDocument doc; doc.setContent (m_temp, true); QXmlStreamWriter writer (&m_output); writer.writeStartDocument (QString::fromUtf8 ("1.0")); process (doc.documentElement (), m_top_eval, writer); writer.writeEndDocument (); ret_value = true; } catch (tl::Exception &ex) { tl::error << ex.msg (); QTextStream writer (&m_output); writer << tl::to_string (QObject::tr ("ERROR: evaluating template: ")).c_str () << ex.msg ().c_str (); } m_output.close (); return ret_value; } void StatisticsTemplateProcessor::process_child_nodes (const QDomElement &element, tl::Eval &eval, QXmlStreamWriter &writer) { if (element.isNull ()) { return; } for (QDomNode n = element.firstChild (); ! n.isNull (); n = n.nextSibling ()) { if (n.isElement ()) { process (n.toElement (), eval, writer); } else if (n.isCDATASection ()) { writer.writeCDATA (tl::to_qstring (eval.interpolate (tl::to_string (n.toCDATASection ().data ())))); } else if (n.isCharacterData ()) { QString t; QTextStream s (&t); while (true) { s << n.toCharacterData ().data (); QDomNode nn = n.nextSibling (); if (nn.isNull () || ! nn.isCharacterData ()) { break; } n = nn; } writer.writeCharacters (tl::to_qstring (eval.interpolate (tl::to_string (t)))); } } } void StatisticsTemplateProcessor::process (const QDomElement &element, tl::Eval &eval, QXmlStreamWriter &writer) { static QString template_namespace_uri = QString::fromUtf8 ("www.klayout.de/layout-statistics-template"); static QString template_cmd_if = QString::fromUtf8 ("if"); static QString template_cmd_true = QString::fromUtf8 ("true"); static QString template_cmd_false = QString::fromUtf8 ("false"); static QString template_cmd_eval = QString::fromUtf8 ("eval"); static QString template_cmd_query = QString::fromUtf8 ("query"); static QString template_cmd_begin = QString::fromUtf8 ("begin"); static QString template_cmd_end = QString::fromUtf8 ("end"); static QString template_cmd_max = QString::fromUtf8 ("max"); static QString template_cmd_each = QString::fromUtf8 ("each"); static QString template_value_true = QString::fromUtf8 ("true"); static QString template_value_empty_query = QString::fromUtf8 (""); static QString template_name_expr = QString::fromUtf8 ("expr"); static QString template_name_max = QString::fromUtf8 ("max"); if (element.namespaceURI () == template_namespace_uri) { if (element.localName () == template_cmd_eval) { tl::Expression expr; eval.parse (expr, tl::to_string (element.attribute (template_name_expr, template_value_true))); expr.execute (); } else if (element.localName () == template_cmd_if) { QDomElement true_node, false_node; for (QDomNode n = element.firstChild (); ! n.isNull (); n = n.nextSibling ()) { QDomElement e = n.toElement (); if (! e.isNull () && e.namespaceURI () == template_namespace_uri) { if (e.localName () == template_cmd_true) { true_node = e; } else if (e.localName () == template_cmd_false) { false_node = e; } } } if (true_node.isNull () && false_node.isNull ()) { true_node = element; } tl::Expression expr; eval.parse (expr, tl::to_string (element.attribute (template_name_expr, template_value_true))); tl::Variant value = expr.execute (); if (value.to_bool ()) { if (! true_node.isNull ()) { process (true_node, eval, writer); } } else { if (! false_node.isNull ()) { process (false_node, eval, writer); } } } else if (element.localName () == template_cmd_query) { QDomElement begin_node, end_node, each_node, max_node; for (QDomNode n = element.firstChild (); ! n.isNull (); n = n.nextSibling ()) { QDomElement e = n.toElement (); if (! e.isNull () && e.namespaceURI () == template_namespace_uri) { if (e.localName () == template_cmd_begin) { begin_node = e; } else if (e.localName () == template_cmd_end) { end_node = e; } else if (e.localName () == template_cmd_max) { max_node = e; } else if (e.localName () == template_cmd_each) { each_node = e; } } } if (begin_node.isNull () && end_node.isNull () && max_node.isNull () && each_node.isNull ()) { each_node = element; } unsigned long max_count = std::numeric_limits::max (); QString max_attr = element.attribute (template_name_max, QString ()); if (! max_attr.isNull ()) { tl::Expression expr; eval.parse (expr, tl::to_string (max_attr)); tl::Variant max = expr.execute (); if (max.can_convert_to_ulong ()) { max_count = max.to_ulong (); } } db::LayoutQuery q (tl::to_string (element.attribute (template_name_expr, template_value_empty_query))); db::LayoutQueryIterator qi (q, mp_layout, &eval); process_child_nodes (begin_node, qi.eval (), writer); for ( ; !qi.at_end (); ++qi) { if (max_count == 0) { process_child_nodes (max_node, qi.eval (), writer); break; } else { --max_count; process_child_nodes (each_node, qi.eval (), writer); } } process_child_nodes (end_node, qi.eval (), writer); } } else { writer.writeStartElement (element.nodeName ()); if (element.hasAttributes ()) { // Hint: attribute nodes are not children of the elements .. QDomNamedNodeMap attributes = element.attributes (); for (int i = 0; i < attributes.count (); ++i) { QDomAttr a = attributes.item (i).toAttr (); if (! a.isNull ()) { writer.writeAttribute (a.nodeName (), tl::to_qstring (eval.interpolate (tl::to_string (a.value ())))); } } } process_child_nodes (element, eval, writer); writer.writeEndElement (); } } // ------------------------------------------------------------ class StatisticsSource : public lay::BrowserSource { public: StatisticsSource (const lay::LayoutHandleRef &h) : m_h (h) { // .. nothing yet .. } std::string get (const std::string &url); private: const lay::LayoutHandleRef m_h; }; std::string StatisticsSource::get (const std::string &url) { QUrl qurl (tl::to_qstring (url)); QFileInfo fi (qurl.path ()); if (fi.suffix () == QString::fromUtf8 ("stxml")) { StatisticsTemplateProcessor tp (qurl, &m_h->layout ()); tp.process (); std::string r = tp.get ().constData (); return r; } else { // This is the default top level page // TODO: handle other input as well const db::Layout &layout = m_h->layout (); std::ostringstream os; os.imbue (std::locale ("C")); size_t num_cells = layout.cells (); size_t num_layers = 0; for (unsigned int i = 0; i < layout.layers (); ++i) { if (layout.is_valid_layer (i)) { ++num_layers; } } os << "" << std::endl << "" << std::endl << "

" << tl::to_string (QObject::tr ("Common Statistics For '")) << m_h->name () << "'

" << std::endl << "" << std::endl << "" << "" << "" << std::endl << "" << "" << "" << std::endl << "" << "" << "" << std::endl << "" << "" << "" << std::endl << "" << "" << "" << std::endl; for (db::Layout::meta_info_iterator meta = layout.begin_meta (); meta != layout.end_meta (); ++meta) { os << "" << std::endl; } os << "
" << tl::to_string (QObject::tr ("Path")) << ": " << m_h->filename () << "
" << tl::to_string (QObject::tr ("Technology")) << ": " << m_h->technology ()->description () << format_tech_name (m_h->tech_name ()) << "
" << tl::to_string (QObject::tr ("Database unit")) << ": " << tl::sprintf ("%.12g ", layout.dbu ()) << tl::to_string (QObject::tr ("micron")) << "
" << tl::to_string (QObject::tr ("Number of cells")) << ": " << num_cells << "
" << tl::to_string (QObject::tr ("Number of layers")) << ": " << num_layers << "
" << meta->description << "" << meta->value << "
" << std::endl << "

" << tl::to_string (QObject::tr ("Top Cells")) << "

" << std::endl << "" << std::endl; for (db::Layout::top_down_const_iterator tc = layout.begin_top_down (); tc != layout.end_top_cells (); ++tc) { os << "" << std::endl; } os << "
" << layout.cell_name (*tc) << "
" << std::endl; std::vector layers_with_oasis_names; std::vector layers_sorted_by_ld; layers_sorted_by_ld.reserve (layout.layers ()); for (unsigned int i = 0; i < layout.layers (); ++i) { layers_sorted_by_ld.push_back (i); const db::LayerProperties &lp = layout.get_properties (i); if (! lp.name.empty ()) { layers_with_oasis_names.push_back (i); } } std::sort (layers_sorted_by_ld.begin (), layers_sorted_by_ld.end (), CompareLDName (layout)); std::sort (layers_with_oasis_names.begin (), layers_with_oasis_names.end (), CompareNameLD (layout)); os << "

" << tl::to_string (QObject::tr ("Layers (sorted by layer and datatype)")) << "

" << std::endl << "" << std::endl << ""; if (! layers_with_oasis_names.empty ()) { os << ""; } os << "" << std::endl; for (std::vector ::const_iterator i = layers_sorted_by_ld.begin (); i != layers_sorted_by_ld.end (); ++i) { if (layout.is_valid_layer (*i)) { const db::LayerProperties &lp = layout.get_properties (*i); os << "" << ""; if (! layers_with_oasis_names.empty ()) { os << ""; } os << "" << std::endl; } } os << "
" << tl::to_string (QObject::tr ("Layer/Datatype")) << "  " << tl::to_string (QObject::tr ("OASIS layer name")) << "
" << tl::sprintf ("%d/%d", lp.layer, lp.datatype) << "" << lp.name << "
" << std::endl; if (! layers_with_oasis_names.empty ()) { os << "

" << tl::to_string (QObject::tr ("Layers (sorted by OASIS layer names)")) << "

" << std::endl << "" << std::endl << "" << std::endl; for (std::vector ::const_iterator i = layers_with_oasis_names.begin (); i != layers_with_oasis_names.end (); ++i) { if (layout.is_valid_layer (*i)) { const db::LayerProperties &lp = layout.get_properties (*i); if (! lp.name.empty ()) { os << "" << "" << "" << "" << std::endl; } } } os << "
" << tl::to_string (QObject::tr ("OASIS layer name")) << "  " << tl::to_string (QObject::tr ("Layer/Datatype")) << "
" << lp.name << "" << tl::sprintf ("%d/%d", lp.layer, lp.datatype) << "
" << std::endl; } os << "" << std::endl << "" << std::endl; ; return os.str (); } } // ------------------------------------------------------------ LayoutStatisticsForm::LayoutStatisticsForm (QWidget *parent, lay::LayoutView *view, const char *name) : QDialog (parent), Ui::LayoutStatisticsForm (), mp_source (0) { setObjectName (QString::fromUtf8 (name)); Ui::LayoutStatisticsForm::setupUi (this); // collect the distinct layout handles std::set handles; for (unsigned int n = 0; n < view->cellviews (); ++n) { handles.insert (view->cellview (n).operator-> ()); } m_handles.reserve (handles.size ()); for (unsigned int n = 0; n < view->cellviews (); ++n) { lay::LayoutHandle *h = view->cellview (n).operator-> (); if (handles.find (h) != handles.end ()) { m_handles.push_back (h); handles.erase (h); layout_cbx->addItem (tl::to_qstring (h->name ())); } } layout_cbx->setCurrentIndex (view->active_cellview_index ()); connect (layout_cbx, SIGNAL (activated (int)), this, SLOT (layout_selected (int))); layout_selected (layout_cbx->currentIndex ()); } LayoutStatisticsForm::~LayoutStatisticsForm () { browser->set_source (0); if (mp_source != 0) { delete mp_source; mp_source = 0; } } void LayoutStatisticsForm::layout_selected (int index) { if (index >= int (m_handles.size ()) || index < 0) { return; } browser->set_source (0); if (mp_source != 0) { delete mp_source; } mp_source = new StatisticsSource (m_handles [index]); browser->set_source (mp_source); browser->set_home ("int:index"); browser->home (); } }