klayout/src/gtfui/gtfUiDialog.cc

742 lines
22 KiB
C++

/*
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 "gtfUiDialog.h"
#include "ui_gtfUiDialogUI.h"
#include "tlLog.h"
#include <iostream>
#include <sstream>
#include <functional>
#include <QScrollBar>
#include <QPainter>
#include <QHeaderView>
#include <QTreeView>
#include <QTreeWidget>
namespace gtf
{
static QBrush left_diff_brush (QColor (192, 64, 64));
static QBrush left_diff_brush_dep (QColor (192, 64, 64), Qt::Dense4Pattern);
static QBrush right_diff_brush (QColor (64, 192, 64));
static QBrush right_diff_brush_dep (QColor (64, 192, 64), Qt::Dense4Pattern);
// --------------------------------------------------------------------------
// diff implementation
/**
* @brief diff two sequences [b1,e1) [b2,e2)
*
* The output is delivered to these output iterators:
* "common" will receive all elements that are common to stream 1 and 2,
* "o1" those that only appear in stream 1, "o2" only those that appear in stream 2,
* "d" will receive std::pair's of elements that differ between 1 and 2.
* Two parameters govern the algorithm:
* "max_lookahead": tell how far to look into the future to find "synchronized" parts.
* "min_sync": how many elements must be equal (in sequence) to resync.
*/
template <class I, class O1, class O2, class O3, class O4, class EQ>
void diff (I b1, I e1, I b2, I e2, O1 common, O2 o1, O3 o2, O4 d, EQ equal = std::equal_to<typename std::iterator_traits<I>::value_type> (), unsigned long max_lookahead = 100, unsigned long min_sync = 3)
{
I i1 = b1;
I i2 = b2;
while (i1 != e1 && i2 != e2) {
if (equal (*i1, *i2)) {
*common = std::make_pair (*i1, *i2);
++common;
++i1;
++i2;
} else {
bool sync1 = false;
unsigned long s1 = 1;
for (I ii1 = i1; ++ii1 != e1 && s1 < max_lookahead; ++s1) {
unsigned long s = min_sync;
for (I j1 = ii1, j2 = i2; s > 0 && j1 != e1 && j2 != e2 && equal (*j1, *j2); ++j1, ++j2) {
--s;
}
if (! s) {
sync1 = true;
break;
}
}
bool sync2 = false;
unsigned long s2 = 1;
for (I ii2 = i2; ++ii2 != e2 && s2 < max_lookahead; ++s2) {
unsigned long s = min_sync;
for (I j1 = i1, j2 = ii2; s > 0 && j1 != e1 && j2 != e2 && equal (*j1, *j2); ++j1, ++j2) {
--s;
}
if (! s) {
sync2 = true;
break;
}
}
if (sync1 && (!sync2 || s1 < s2)) {
while (s1 > 0) {
*o1 = *i1;
++o1;
++i1;
--s1;
}
} else if (sync2 && (!sync1 || s2 < s1)) {
while (s2 > 0) {
*o2 = *i2;
++o2;
++i2;
--s2;
}
} else {
*d = std::make_pair (*i1, *i2);
++d;
++i1;
++i2;
}
}
}
while (i1 != e1) {
*o1 = *i1;
++i1;
++o1;
}
while (i2 != e2) {
*o2 = *i2;
++i2;
++o2;
}
}
// --------------------------------------------------------------------------
// StripedBar definition and implementation
StripedBar::StripedBar (QWidget *parent)
: QFrame (parent), mp_tv (0)
{
QObjectList cc = children ();
for (QObjectList::const_iterator child = cc.begin (); child != cc.end (); ++child) {
QWidget *ww = dynamic_cast <QWidget *> (*child);
if (ww) {
delete ww;
}
}
// ...
}
void
StripedBar::paintEvent (QPaintEvent *e)
{
QFrame::paintEvent (e);
if (mp_tv) {
QPainter painter (this);
QAbstractItemModel *model = mp_tv->model ();
int fw = 0;
int lw = lineWidth ();
int y = lw;
int h = 1;
int htot = height () - 2 * lw;
int wtot = width () - 2 * (lw + fw);
int count = 0;
QModelIndex mi (model->index (0, 1));
while (mi.isValid ()) {
++count;
mi = mp_tv->indexBelow (mi);
}
int ytop = -1;
int ybottom = -1;
int index = 0;
QModelIndex col0 (model->index (0, 0));
while (col0.isValid ()) {
QModelIndex col1 (model->index (col0.row (), 1, model->parent (col0)));
y = (index * htot) / count;
h = ((index + 1) * htot) / count - y;
if (h == 0) {
h = 1;
}
QRect r (mp_tv->visualRect (col0));
if (r.bottom () >= 0 && r.top () < height ()) {
if (ytop < 0) {
ytop = y;
}
ybottom = y + h;
}
if (model->data (col0, Qt::UserRole).toBool ()) {
painter.fillRect (lw + fw, y, wtot / 2, h, left_diff_brush);
}
if (model->data (col1, Qt::UserRole).toBool ()) {
painter.fillRect (lw + fw + wtot / 2, y, wtot / 2, h, right_diff_brush);
}
col0 = mp_tv->indexBelow (col0);
++index;
}
if (ytop >= 0) {
painter.fillRect (lw, ytop, width () - 2 * lw, ybottom - ytop, QColor (128, 128, 128, 128));
}
}
}
void
StripedBar::set_treeview (QTreeView *tv)
{
mp_tv = tv;
connect (mp_tv->verticalScrollBar () /* Qt4.2 only*/, SIGNAL (valueChanged (int)), this, SLOT (force_update (int)));
connect (mp_tv, SIGNAL (expanded (const QModelIndex &)), this, SLOT (force_update (const QModelIndex &)));
connect (mp_tv, SIGNAL (collapsed (const QModelIndex &)), this, SLOT (force_update (const QModelIndex &)));
}
void
StripedBar::force_update (int)
{
update ();
}
void
StripedBar::force_update (const QModelIndex &)
{
update ();
}
// --------------------------------------------------------------------------
// UiDialog implementation
static void diff_log_event_list (QTreeWidget *tv, QTreeWidgetItem *parent, const tl::Variant &dleft, const tl::Variant &dright);
static void diff_log_event_data (QTreeWidget *tv, QTreeWidgetItem *parent, const tl::Variant &data_left, const tl::Variant &data_right);
static void add_log_event_list (QTreeWidget *tv, int column, QTreeWidgetItem *parent, const tl::Variant &dlist);
static void add_log_event_data (QTreeWidget *tv, int column, QTreeWidgetItem *parent, const tl::Variant &data);
void
expand_path (QTreeWidget *tv, QTreeWidgetItem *item)
{
tv->expandItem (item);
if (item->parent ()) {
expand_path (tv, item->parent ());
}
}
static std::string
log_event_to_text (const gtf::LogEventBase *e)
{
std::string t = e->name ();
/* too much:
std::vector< std::pair<std::string, std::string> > attrs;
e->attributes (attrs);
for (std::vector< std::pair<std::string, std::string> >::const_iterator a = attrs.begin (); a != attrs.end (); ++a) {
t += " ";
t += a->first;
t += ":";
t += a->second;
}
*/
return t;
}
struct make_entry_both
{
make_entry_both (QTreeWidget *tv) : mp_tv (tv) { }
void operator= (std::pair<const gtf::LogEventBase *, const gtf::LogEventBase *> e) const
{
QTreeWidgetItem *item = new QTreeWidgetItem ();
item->setText (0, log_event_to_text (e.first).c_str ());
item->setText (1, log_event_to_text (e.second).c_str ());
add_log_event_data (mp_tv, -1, item, e.first->data ());
item->setData (0, Qt::UserRole + 2, QVariant::fromValue ((void *)e.first));
item->setData (1, Qt::UserRole + 2, QVariant::fromValue ((void *)e.second));
mp_tv->addTopLevelItem (item);
}
make_entry_both &operator* () { return *this; }
make_entry_both &operator++ (int) { return *this; }
make_entry_both &operator++ () { return *this; }
private:
QTreeWidget *mp_tv;
};
struct make_entry_left
{
make_entry_left (QTreeWidget *tv) : mp_tv (tv) { }
void operator= (const gtf::LogEventBase *e) const
{
QTreeWidgetItem *item = new QTreeWidgetItem ();
std::string t = log_event_to_text (e);
item->setText (0, t.c_str ());
add_log_event_data (mp_tv, 0, item, e->data ());
item->setData (0, Qt::BackgroundRole, QVariant (left_diff_brush));
item->setData (0, Qt::UserRole, QVariant (true));
item->setData (0, Qt::UserRole + 2, QVariant::fromValue ((void *)e));
mp_tv->addTopLevelItem (item);
mp_tv->expandItem (item);
}
make_entry_left &operator* () { return *this; }
make_entry_left &operator++ (int) { return *this; }
make_entry_left &operator++ () { return *this; }
private:
QTreeWidget *mp_tv;
};
struct make_entry_right
{
make_entry_right (QTreeWidget *tv) : mp_tv (tv) { }
void operator= (const gtf::LogEventBase *e) const
{
QTreeWidgetItem *item = new QTreeWidgetItem ();
std::string t = log_event_to_text (e);
item->setText (1, t.c_str ());
add_log_event_data (mp_tv, 1, item, e->data ());
item->setData (1, Qt::BackgroundRole, QVariant (right_diff_brush));
item->setData (1, Qt::UserRole, QVariant (true));
item->setData (1, Qt::UserRole + 2, QVariant::fromValue ((void *)e));
mp_tv->addTopLevelItem (item);
mp_tv->expandItem (item);
}
make_entry_right &operator* () { return *this; }
make_entry_right &operator++ (int) { return *this; }
make_entry_right &operator++ () { return *this; }
private:
QTreeWidget *mp_tv;
};
struct make_entry_diff
{
make_entry_diff (QTreeWidget *tv) : mp_tv (tv) { }
void operator= (std::pair<const gtf::LogEventBase *, const gtf::LogEventBase *> e) const
{
QTreeWidgetItem *item = new QTreeWidgetItem ();
std::string tleft = log_event_to_text (e.first);
std::string tright = log_event_to_text (e.second);
bool dep = e.first->equals (*e.second);
item->setText (0, tleft.c_str ());
item->setData (0, Qt::BackgroundRole, QVariant (dep ? left_diff_brush_dep : left_diff_brush));
item->setData (0, Qt::UserRole, QVariant (true));
item->setData (0, Qt::UserRole + 2, QVariant::fromValue ((void *)e.first));
item->setText (1, tright.c_str ());
item->setData (1, Qt::BackgroundRole, QVariant (dep ? right_diff_brush_dep : right_diff_brush));
item->setData (1, Qt::UserRole, QVariant (true));
item->setData (1, Qt::UserRole + 2, QVariant::fromValue ((void *)e.second));
diff_log_event_data (mp_tv, item, e.first->data (), e.second->data ());
mp_tv->addTopLevelItem (item);
mp_tv->expandItem (item);
}
make_entry_diff &operator* () { return *this; }
make_entry_diff &operator++ (int) { return *this; }
make_entry_diff &operator++ () { return *this; }
private:
QTreeWidget *mp_tv;
};
struct ptr_eq
{
bool operator() (const gtf::LogEventBase *a, const gtf::LogEventBase *b) const
{
return *a == *b;
}
};
static void
add_log_event (QTreeWidget *tv, int column, QTreeWidgetItem *parent, const tl::Variant &d, QTreeWidgetItem *p = 0)
{
if (! p) {
p = new QTreeWidgetItem (parent);
}
std::string t;
if (d.is_list ()) {
t = "block";
add_log_event_list (tv, column, p, d);
} else if (d.is_user () && d.user_type () == 1) {
t = "img";
if (column < 0) {
p->setData (0, Qt::UserRole + 1, d.to_user<QImage> ());
p->setData (1, Qt::UserRole + 1, d.to_user<QImage> ());
} else {
p->setData (column, Qt::UserRole + 1, d.to_user<QImage> ());
}
} else if (d.is_long ()) {
t = "int ";
t += d.to_string ();
} else if (d.is_string ()) {
t = "string \"";
t += d.to_string ();
t += "\"";
}
if (column < 0) {
p->setText (0, t.c_str ());
p->setText (1, t.c_str ());
} else {
p->setText (column, t.c_str ());
if (column == 0) {
p->setData (0, Qt::BackgroundRole, QVariant (left_diff_brush));
p->setData (0, Qt::UserRole, QVariant (true));
expand_path (tv, p);
} else {
p->setData (1, Qt::BackgroundRole, QVariant (right_diff_brush));
p->setData (1, Qt::UserRole, QVariant (true));
expand_path (tv, p);
}
}
}
static void
add_log_event_list (QTreeWidget *tv, int column, QTreeWidgetItem *parent, const tl::Variant &dlist)
{
for (tl::Variant::iterator d = dlist.begin (); d != dlist.end (); ++d) {
add_log_event (tv, column, parent, *d);
}
}
static void
add_log_event_data (QTreeWidget *tv, int column, QTreeWidgetItem *parent, const tl::Variant &data)
{
if (! data.is_nil ()) {
tl::Variant ddummy = tl::Variant::empty_list ();
const tl::Variant *d = &data;
if (! d->is_list ()) {
ddummy.push (data);
d = &ddummy;
}
add_log_event_list (tv, column, parent, *d);
}
}
struct enter_data
{
enter_data (QTreeWidget *tv, QTreeWidgetItem *item, int column) : mp_tv (tv), mp_item (item), m_column (column) { }
enter_data &operator= (const tl::Variant &data) { add_log_event (mp_tv, m_column, mp_item, data); return *this; }
enter_data &operator= (const std::pair<tl::Variant, tl::Variant> &data) { return *this = data.first; }
enter_data &operator* () { return *this; }
enter_data &operator++ () { return *this; }
enter_data &operator++ (int) { return *this; }
private:
QTreeWidget *mp_tv;
QTreeWidgetItem *mp_item;
int m_column;
};
struct enter_data_diff
{
enter_data_diff (QTreeWidget *tv, QTreeWidgetItem *item) : mp_tv (tv), mp_item (item) { }
enter_data_diff &operator= (const std::pair<tl::Variant, tl::Variant> &data)
{
QTreeWidgetItem *p = new QTreeWidgetItem (mp_item);
if (data.first.is_list () && data.second.is_list ()) {
p->setText (0, "block");
p->setData (0, Qt::BackgroundRole, QVariant (left_diff_brush_dep));
p->setData (0, Qt::UserRole, QVariant (true));
p->setText (1, "block");
p->setData (1, Qt::BackgroundRole, QVariant (right_diff_brush_dep));
p->setData (1, Qt::UserRole, QVariant (true));
diff_log_event_list (mp_tv, p, data.first, data.second);
} else if (data.first.is_list ()) {
p->setText (0, "block");
p->setData (0, Qt::BackgroundRole, QVariant (left_diff_brush));
p->setData (0, Qt::UserRole, QVariant (true));
expand_path (mp_tv, p);
add_log_event_list (mp_tv, 0, mp_item, data.first);
add_log_event (mp_tv, 1, mp_item, data.second, p);
} else if (data.second.is_list ()) {
p->setText (1, "block");
p->setData (1, Qt::BackgroundRole, QVariant (right_diff_brush));
p->setData (1, Qt::UserRole, QVariant (true));
expand_path (mp_tv, p);
add_log_event_list (mp_tv, 1, mp_item, data.second);
add_log_event (mp_tv, 0, mp_item, data.first, p);
} else {
add_log_event (mp_tv, 0, mp_item, data.first, p);
add_log_event (mp_tv, 1, mp_item, data.second, p);
}
return *this;
}
enter_data_diff &operator* () { return *this; }
enter_data_diff &operator++ () { return *this; }
enter_data_diff &operator++ (int) { return *this; }
private:
QTreeWidget *mp_tv;
QTreeWidgetItem *mp_item;
};
static void
diff_log_event_list (QTreeWidget *tv, QTreeWidgetItem *parent, const tl::Variant &dleft, const tl::Variant &dright)
{
enter_data both (tv, parent, -1);
enter_data left (tv, parent, 0);
enter_data right (tv, parent, 1);
enter_data_diff delta (tv, parent);
diff (dleft.begin (), dleft.end (), dright.begin (), dright.end (), both, left, right, delta, std::equal_to<tl::Variant> ());
}
static void
diff_log_event_data (QTreeWidget *tv, QTreeWidgetItem *parent, const tl::Variant &data_left, const tl::Variant &data_right)
{
tl::Variant ddummyleft = tl::Variant::empty_list ();
const tl::Variant *dleft = &data_left;
if (dleft->is_nil ()) {
dleft = &ddummyleft;
} else if (! dleft->is_list ()) {
ddummyleft.push (data_left);
dleft = &ddummyleft;
}
tl::Variant ddummyright = tl::Variant::empty_list ();
const tl::Variant *dright = &data_right;
if (dright->is_nil ()) {
dright = &ddummyright;
} else if (! dright->is_list ()) {
ddummyright.push (data_right);
dright = &ddummyright;
}
diff_log_event_list (tv, parent, *dleft, *dright);
}
UiDialog::UiDialog ()
{
mp_ui = new Ui_GtfUiDialog ();
mp_ui->setupUi (this);
mp_ui->striped_bar->set_treeview (mp_ui->log_list);
mp_ui->log_list->header ()->setResizeMode (QHeaderView::Stretch);
mp_ui->au_event_list->header ()->setResizeMode (QHeaderView::ResizeToContents);
mp_ui->curr_event_list->header ()->setResizeMode (QHeaderView::ResizeToContents);
mp_ui->golden_img_frame->setWidget (mp_ui->golden_lbl);
mp_ui->delta_img_frame->setWidget (mp_ui->delta_lbl);
mp_ui->current_img_frame->setWidget (mp_ui->current_lbl);
connect (mp_ui->actionExit, SIGNAL (triggered ()), qApp, SLOT (quit ()));
connect (mp_ui->log_list, SIGNAL (currentItemChanged (QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT (item_selected (QTreeWidgetItem *, QTreeWidgetItem *)));
// ...
}
UiDialog::~UiDialog ()
{
delete mp_ui;
mp_ui = 0;
}
void
UiDialog::open_files (const std::string &fn_au, const std::string &fn_current)
{
m_fn_au = fn_au;
m_fn_current = fn_current;
if (tl::verbosity () >= 10) {
tl::info << "Reading golden file: " << fn_au;
}
m_au_events.load (fn_au, true /*no spontaneous*/);
if (tl::verbosity () >= 10) {
tl::info << "Reading current file: " << fn_current;
}
m_current_events.load (fn_current, true /*no spontaneous*/);
make_entry_both b (mp_ui->log_list);
make_entry_left l (mp_ui->log_list);
make_entry_right r (mp_ui->log_list);
make_entry_diff d (mp_ui->log_list);
diff (m_au_events.begin (), m_au_events.end (), m_current_events.begin (), m_current_events.end (), b, l, r, d, ptr_eq ());
}
void
UiDialog::item_selected (QTreeWidgetItem *current, QTreeWidgetItem *)
{
if (current->data (0, Qt::UserRole + 1) != QVariant () ||
current->data (1, Qt::UserRole + 1) != QVariant ()) {
mp_ui->compare_stck->setCurrentIndex (1);
QImage img_left;
if (current->data (0, Qt::UserRole + 1).type () == QVariant::Image) {
img_left = current->data (0, Qt::UserRole + 1).value<QImage> ();
}
QImage img_right;
if (current->data (1, Qt::UserRole + 1).type () == QVariant::Image) {
img_right = current->data (1, Qt::UserRole + 1).value<QImage> ();
}
if (! img_left.isNull ()) {
QPixmap pixmap;
pixmap = img_left; // Qt 4.6.0 workaround
mp_ui->golden_lbl->setPixmap (pixmap);
mp_ui->golden_lbl->resize (img_left.size ());
} else {
mp_ui->golden_lbl->setPixmap (QPixmap ());
mp_ui->golden_lbl->setText ("");
}
if (! img_right.isNull ()) {
QPixmap pixmap;
pixmap = img_right; // Qt 4.6.0 workaround
mp_ui->current_lbl->setPixmap (pixmap);
mp_ui->current_lbl->resize (img_right.size ());
} else {
mp_ui->current_lbl->setPixmap (QPixmap ());
mp_ui->current_lbl->setText ("");
}
if (! img_left.isNull () && ! img_right.isNull ()) {
int w = std::min (img_left.width (), img_right.width ());
int h = std::min (img_left.height (), img_right.height ());
QImage delta (w, h, QImage::Format_RGB32);
for (int x = 0; x < w; ++x) {
for (int y = 0; y < h; ++y) {
QRgb p1 = img_left.pixel (x, y);
QRgb p2 = img_right.pixel (x, y);
QRgb px = p1 ^ p2;
delta.setPixel (x, y, px);
}
}
QPixmap pixmap;
pixmap = delta; // Qt 4.6.0 workaround
mp_ui->delta_lbl->setPixmap (pixmap);
mp_ui->delta_lbl->resize (delta.size ());
} else {
mp_ui->delta_lbl->setPixmap (QPixmap ());
mp_ui->delta_lbl->setText ("");
}
} else if (current->data (0, Qt::UserRole + 2) != QVariant () ||
current->data (1, Qt::UserRole + 2) != QVariant ()) {
mp_ui->compare_stck->setCurrentIndex (2);
std::vector< std::pair<std::string, std::string> > attrs_left, attrs_right;
mp_ui->au_event_list->clear ();
mp_ui->curr_event_list->clear ();
const LogEventBase *eleft = 0;
const LogEventBase *eright = 0;
if (current->data (0, Qt::UserRole + 2) != QVariant ()) {
eleft = (const LogEventBase *) current->data (0, Qt::UserRole + 2).value<void *> ();
eleft->attributes (attrs_left);
}
if (current->data (1, Qt::UserRole + 2) != QVariant ()) {
eright = (const LogEventBase *) current->data (1, Qt::UserRole + 2).value<void *> ();
eright->attributes (attrs_right);
}
bool same_type = false;
if (eleft != 0 && eright != 0 && std::string (eleft->name ()) == eright->name ()) {
same_type = true;
}
std::vector< std::pair<std::string, std::string> >::const_iterator l = attrs_left.begin ();
std::vector< std::pair<std::string, std::string> >::const_iterator r = attrs_right.begin ();
while (l != attrs_left.end () || r != attrs_right.end ()) {
if (l != attrs_left.end ()) {
QTreeWidgetItem *item = new QTreeWidgetItem (mp_ui->au_event_list);
item->setText (0, l->first.c_str ());
item->setText (1, l->second.c_str ());
if (! same_type || (r != attrs_right.end () && l->second != r->second)) {
item->setData (1, Qt::BackgroundRole, QVariant (left_diff_brush));
}
}
if (r != attrs_right.end ()) {
QTreeWidgetItem *item = new QTreeWidgetItem (mp_ui->curr_event_list);
item->setText (0, r->first.c_str ());
item->setText (1, r->second.c_str ());
if (! same_type || (l != attrs_left.end () && l->second != r->second)) {
item->setData (1, Qt::BackgroundRole, QVariant (right_diff_brush));
}
}
if (r != attrs_right.end ()) {
++r;
}
if (l != attrs_left.end ()) {
++l;
}
}
QTreeWidgetItem *item;
if (eleft) {
item = new QTreeWidgetItem (mp_ui->au_event_list);
item->setText (0, "XML line");
item->setText (1, tl::to_string (eleft->xml_line ()).c_str ());
}
if (eright) {
item = new QTreeWidgetItem (mp_ui->curr_event_list);
item->setText (0, "XML line");
item->setText (1, tl::to_string (eright->xml_line ()).c_str ());
}
} else {
mp_ui->compare_stck->setCurrentIndex (0);
}
}
}