mirror of https://github.com/KLayout/klayout.git
589 lines
18 KiB
C++
589 lines
18 KiB
C++
|
|
/*
|
|
|
|
KLayout Layout Viewer
|
|
Copyright (C) 2006-2017 Matthias Koefferlein
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
|
|
#include "layLayoutStatisticsForm.h"
|
|
#include "layLayoutView.h"
|
|
#include "tlInternational.h"
|
|
#include "tlString.h"
|
|
#include "tlExpression.h"
|
|
#include "tlTimer.h"
|
|
#include "dbLayoutQuery.h"
|
|
|
|
#include <set>
|
|
#include <sstream>
|
|
|
|
#include <QBuffer>
|
|
#include <QResource>
|
|
#include <QFileInfo>
|
|
#include <QDomElement>
|
|
#include <QDomDocument>
|
|
#include <QXmlStreamWriter>
|
|
#if QT_VERSION >= 0x050000
|
|
# include <QUrlQuery>
|
|
#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/<path>" 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<QPair<QString, QString> > queryItems = QUrlQuery (url.query ()).queryItems ();
|
|
#else
|
|
QList<QPair<QString, QString> > queryItems = url.queryItems ();
|
|
#endif
|
|
for (QList<QPair<QString, QString> >::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<unsigned long>::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 << "<html>" << std::endl
|
|
<< "<body>" << std::endl
|
|
<< "<h2>" << tl::to_string (QObject::tr ("Common Statistics For '")) << m_h->name () << "'</h2>" << std::endl
|
|
<< "<table>" << std::endl
|
|
<< "<tr>"
|
|
<< "<td>" << tl::to_string (QObject::tr ("Path")) << ": </td><td>" << m_h->filename () << "</td>"
|
|
<< "</tr>" << std::endl
|
|
<< "<tr>"
|
|
<< "<td>" << tl::to_string (QObject::tr ("Technology")) << ": </td><td>" << m_h->technology ()->description () << format_tech_name (m_h->tech_name ()) << "</td>"
|
|
<< "</tr>" << std::endl
|
|
<< "<tr>"
|
|
<< "<td>" << tl::to_string (QObject::tr ("Database unit")) << ": </td><td>" << tl::sprintf ("%.12g ", layout.dbu ()) << tl::to_string (QObject::tr ("micron")) << "</td>"
|
|
<< "</tr>" << std::endl
|
|
<< "<tr>"
|
|
<< "<td>" << tl::to_string (QObject::tr ("Number of cells")) << ": </td><td>" << num_cells << "</td>"
|
|
<< "</tr>" << std::endl
|
|
<< "<tr>"
|
|
<< "<td>" << tl::to_string (QObject::tr ("Number of layers")) << ": </td><td>" << num_layers << "</td>"
|
|
<< "</tr>" << std::endl;
|
|
for (db::Layout::meta_info_iterator meta = layout.begin_meta (); meta != layout.end_meta (); ++meta) {
|
|
os << "<tr><td>" << meta->description << "</td><td>" << meta->value << "</td></tr>" << std::endl;
|
|
}
|
|
os << "</table>" << std::endl
|
|
<< "<h2>" << tl::to_string (QObject::tr ("Top Cells")) << "</h2>" << std::endl
|
|
<< "<table>" << std::endl;
|
|
for (db::Layout::top_down_const_iterator tc = layout.begin_top_down (); tc != layout.end_top_cells (); ++tc) {
|
|
os << "<tr><td>" << layout.cell_name (*tc) << "</td></tr>" << std::endl;
|
|
}
|
|
os << "</table>" << std::endl;
|
|
|
|
std::vector <unsigned int> layers_with_oasis_names;
|
|
|
|
std::vector <unsigned int> 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 << "<h2>" << tl::to_string (QObject::tr ("Layers (sorted by layer and datatype)")) << "</h2>" << std::endl
|
|
<< "<table>" << std::endl
|
|
<< "<tr><td><b>" << tl::to_string (QObject::tr ("Layer/Datatype")) << "</b> </td>";
|
|
if (! layers_with_oasis_names.empty ()) {
|
|
os << "<td><b>" << tl::to_string (QObject::tr ("OASIS layer name")) << "</b></td>";
|
|
}
|
|
os << "</tr>" << std::endl;
|
|
|
|
for (std::vector <unsigned int>::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 << "<tr>"
|
|
<< "<td>" << tl::sprintf ("%d/%d", lp.layer, lp.datatype) << "</td>";
|
|
if (! layers_with_oasis_names.empty ()) {
|
|
os << "<td>" << lp.name << "</td>";
|
|
}
|
|
os << "</tr>" << std::endl;
|
|
}
|
|
}
|
|
|
|
os << "</table>" << std::endl;
|
|
|
|
if (! layers_with_oasis_names.empty ()) {
|
|
|
|
os << "<h2>" << tl::to_string (QObject::tr ("Layers (sorted by OASIS layer names)")) << "</h2>" << std::endl
|
|
<< "<table>" << std::endl
|
|
<< "<tr><td><b>" << tl::to_string (QObject::tr ("OASIS layer name")) << "</b> </td><td><b>" << tl::to_string (QObject::tr ("Layer/Datatype")) << "</b></td></tr>" << std::endl;
|
|
|
|
for (std::vector <unsigned int>::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 << "<tr>"
|
|
<< "<td>" << lp.name << "</td>"
|
|
<< "<td>" << tl::sprintf ("%d/%d", lp.layer, lp.datatype) << "</td>"
|
|
<< "</tr>" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
os << "</table>" << std::endl;
|
|
|
|
}
|
|
|
|
os << "</body>" << std::endl
|
|
<< "</html>" << 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 <lay::LayoutHandle *> 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 ();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|