Files
klayout/src/laybasic/gtf.cc
T

1933 lines
51 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 "gtf.h"
#include "tlException.h"
#include "tlLog.h"
#include "tlXMLParser.h"
#include "tlAssert.h"
#include "tlVariantUserClasses.h"
#include <QApplication>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QResizeEvent>
#include <QInputEvent>
#include <QWidget>
#include <QToolBar>
#include <QMenuBar>
#include <QComboBox>
#include <QLineEdit>
#include <QTextEdit>
#include <QTreeView>
#include <QListView>
#include <QCheckBox>
#include <QScrollBar>
#include <QDialog>
#include <QMainWindow>
#include <QRadioButton>
#include <QPushButton>
#include <QSpinBox>
#include <QPainter>
#include <QBitmap>
#include <QFile>
#include <QBuffer>
#include <QTimer>
#include <fstream>
#include <sstream>
#include <map>
// --------------------------------------------------------------
// A helper class that allows to put a QImage into a tl::Variant
namespace tl
{
template <>
struct type_traits <QImage> : public type_traits<void>
{
typedef true_tag has_equal_operator;
};
}
namespace gtf
{
static tl::VariantUserClassImpl<QImage> qimage_class_instance;
tl::Variant
image_to_variant (const QImage &img)
{
return tl::Variant (new QImage (img), &qimage_class_instance, true);
}
class GtfEventFilter;
// --------------------------------------------------------------
// Convert a string to a form suitable for XML output
static std::string
escape_string (const char *cp)
{
std::string r;
r.reserve (strlen (cp) * 2);
while (*cp) {
if (*cp == '&') {
r += "&amp;";
} else if (*cp == '<') {
r += "&lt;";
} else if (*cp == '>') {
r += "&gt;";
} else {
r += *cp;
}
++cp;
}
return r;
}
// --------------------------------------------------------------
// Widget to path conversion and back
inline bool
is_widget (QObject *o)
{
return (dynamic_cast<const QDialog *> (o) != 0 || dynamic_cast<const QMainWindow *> (o) != 0 || dynamic_cast<const QWidget *> (o) != 0);
}
static void
dump_children (QObject *obj, int level = 0)
{
QObjectList children = obj->children ();
std::string info;
for (int i = 0; i < level; ++i) {
info += " ";
}
if (obj->objectName ().isEmpty ()) {
info += "<unnamed>";
} else {
info += tl::to_string (obj->objectName ());
}
info += " (";
info += obj->metaObject ()->className ();
info += tl::sprintf(") - %p", (size_t)obj);
tl::info << info;
for (QObjectList::const_iterator child = children.begin (); child != children.end (); ++child) {
if (is_widget (*child)) {
dump_children (*child, level + 1);
}
}
}
void
dump_widget_tree ()
{
QWidgetList tl_widgets = QApplication::topLevelWidgets ();
tl::info << tl::to_string (QObject::tr ("Widget tree:"));
for (QWidgetList::const_iterator tl = tl_widgets.begin (); tl != tl_widgets.end (); ++tl) {
if (is_widget (*tl)) {
dump_children (*tl);
}
}
tl::info << "";
}
static bool extract_widget_path (tl::Extractor &x, std::string &name, std::string &cls, int &nwidget)
{
name.clear ();
cls.clear ();
nwidget = 1;
if (! x.at_end ()) {
x.read (name, "(.#");
if (*x == '(') {
++x;
x.read (cls, ")#");
if (*x == ')') {
++x;
}
}
if (*x == '#') {
++x;
x.read (nwidget);
}
}
if (! x.test (".")) {
if (! x.at_end ()) {
throw tl::Exception (tl::to_string (QObject::tr ("Invalid widget path: expected '.'")));
}
return false;
} else {
return true;
}
}
static QWidget *
widget_from_path (const char *p, int xml_line)
{
tl::Extractor x (p);
std::string name;
std::string cls;
int nwidget = 1;
bool more = false;
QObject *w = 0;
do {
more = extract_widget_path (x, name, cls, nwidget);
int n = nwidget;
QObject *pw = w;
if (w == 0) {
QWidgetList tl_widgets = QApplication::topLevelWidgets ();
for (QWidgetList::const_iterator tl = tl_widgets.begin (); tl != tl_widgets.end (); ++tl) {
if (is_widget (*tl) && (*tl)->objectName () == tl::to_qstring (name) && (cls.empty () || cls == (*tl)->metaObject ()->className ()) && --n == 0) {
w = *tl;
break;
}
}
} else {
w = 0;
QObjectList children = pw->children ();
for (QObjectList::const_iterator child = children.begin (); child != children.end (); ++child) {
if (is_widget (*child) && (*child)->objectName () == tl::to_qstring (name) && (cls.empty () || cls == (*child)->metaObject ()->className ()) && --n == 0) {
w = *child;
break;
}
}
}
if (w == 0) {
std::string names;
if (pw == 0) {
QWidgetList tl_widgets = QApplication::topLevelWidgets ();
for (QWidgetList::const_iterator tl = tl_widgets.begin (); tl != tl_widgets.end (); ++tl) {
if (is_widget (*tl)) {
if (! names.empty ()) {
names += ",";
}
names += tl::to_string ((*tl)->objectName ());
names += "(";
names += (*tl)->metaObject ()->className ();
names += ")";
}
}
} else {
QObjectList children = pw->children ();
for (QObjectList::const_iterator child = children.begin (); child != children.end (); ++child) {
if (is_widget (*child)) {
if (! names.empty ()) {
names += ",";
}
names += tl::to_string ((*child)->objectName ());
names += "(";
names += (*child)->metaObject ()->className ();
names += ")";
}
}
}
dump_widget_tree ();
throw tl::Exception (tl::to_string (QObject::tr ("Widget path resolution failed: '%s' is not a valid component with index %d in path '%s' (line %d)\nAlternatives are: %s")), name, nwidget, p, xml_line, names);
}
} while (more);
QWidget *target_widget = dynamic_cast<QWidget *> (w);
if (! target_widget) {
throw tl::Exception (tl::to_string (QObject::tr ("Invalid widget path '%s': does not lead to a widget (line %d)")), p, xml_line);
}
return target_widget;
}
static std::string
widget_to_path (QWidget *w, const char *pf = 0)
{
std::string n = tl::to_string (w->objectName ());
std::string cls = w->metaObject ()->className ();
QWidget *pw = w->parentWidget ();
int i = 1;
if (pw) {
QObjectList children = pw->children ();
for (QObjectList::const_iterator child = children.begin (); child != children.end (); ++child) {
if (dynamic_cast<QDialog *> (*child) != 0 || dynamic_cast<QMainWindow *> (*child) != 0 || dynamic_cast <QWidget *> (*child) != 0) {
if (*child == w) {
break;
}
if ((*child)->objectName () == tl::to_qstring (n) && cls == (*child)->metaObject ()->className ()) {
++i;
}
}
}
} else {
QWidgetList tlw = QApplication::topLevelWidgets ();
for (QWidgetList::const_iterator itl = tlw.begin (); itl != tlw.end (); ++itl) {
// only QDialog or QMainWindow anchestors count as valid top level widgets
if (dynamic_cast<QDialog *> (*itl) != 0 || dynamic_cast<QMainWindow *> (*itl) != 0 || dynamic_cast <QWidget *> (*itl) != 0) {
if (*itl == w) {
break;
}
if ((*itl)->objectName () == tl::to_qstring (n) && cls == (*itl)->metaObject ()->className ()) {
++i;
}
}
}
}
n += tl::sprintf ("(%s)", cls);
if (i > 1) {
n += tl::sprintf ("#%d", i);
}
if (pf) {
n += ".";
n += pf;
}
if (pw) {
return widget_to_path (pw, n.c_str ());
} else {
return n;
}
}
// --------------------------------------------------------------
// Widget to track the mouse pointer
class MouseTrackerWidget
: public QWidget
{
public:
MouseTrackerWidget (QWidget *parent);
static MouseTrackerWidget *instance ();
void set (const QMouseEvent &me);
virtual void paintEvent (QPaintEvent *pe);
private:
QPixmap *mp_current_pixmap;
QPixmap m_basic_pm;
QPixmap m_lb_pm;
QPixmap m_mb_pm;
QPixmap m_rb_pm;
};
// pixmaps
MouseTrackerWidget::MouseTrackerWidget (QWidget *parent)
: QWidget (parent,
Qt::Window | Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint),
m_basic_pm (QString::fromUtf8 (":/gtf_basic.png")),
m_lb_pm (QString::fromUtf8 (":/gtf_lb.png")),
m_mb_pm (QString::fromUtf8 (":/gtf_mb.png")),
m_rb_pm (QString::fromUtf8 (":/gtf_rb.png"))
{
mp_current_pixmap = &m_basic_pm;
setAttribute (Qt::WA_NoSystemBackground);
setAttribute (Qt::WA_OpaquePaintEvent);
resize (m_basic_pm.size ());
setMask (m_basic_pm.mask ());
}
MouseTrackerWidget *
MouseTrackerWidget::instance ()
{
static MouseTrackerWidget *inst = 0;
if (inst == 0) {
inst = new MouseTrackerWidget (0);
}
return inst;
}
void
MouseTrackerWidget::set (const QMouseEvent &me)
{
mp_current_pixmap = &m_basic_pm;
if (me.button () == Qt::RightButton) {
mp_current_pixmap = &m_rb_pm;
} else if (me.button () == Qt::MidButton) {
mp_current_pixmap = &m_mb_pm;
} else if (me.button () == Qt::LeftButton) {
mp_current_pixmap = &m_lb_pm;
} else if (me.type () == QEvent::MouseMove) {
if (me.buttons () & Qt::RightButton) {
mp_current_pixmap = &m_rb_pm;
} else if (me.buttons () & Qt::MidButton) {
mp_current_pixmap = &m_mb_pm;
} else if (me.buttons () & Qt::LeftButton) {
mp_current_pixmap = &m_lb_pm;
}
}
show ();
QPoint p = me.globalPos ();
move (p.x () - width () / 2, p.y () - 1);
update ();
}
void
MouseTrackerWidget::paintEvent (QPaintEvent * /*pe*/)
{
QPainter painter (this);
painter.drawPixmap (0, 0, *mp_current_pixmap);
}
// --------------------------------------------------------------
// XML handler declaration
class GtfXmlHandler
: public QXmlDefaultHandler
{
public:
GtfXmlHandler (EventList *list);
bool characters (const QString &ch);
bool endElement (const QString &namespaceURI, const QString &localName, const QString &qName);
bool startElement (const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts);
bool error (const QXmlParseException &exception);
bool fatalError (const QXmlParseException &exception);
bool fatalError (const std::string &msg);
bool warning (const QXmlParseException &exception);
void setDocumentLocator (QXmlLocator *locator);
private:
QXmlLocator *mp_locator;
EventList *mp_list;
std::vector<tl::Variant> m_data_stack;
QString m_cdata;
bool m_in_event;
void enter_event (LogEventBase *event);
void leave_event ();
};
// --------------------------------------------------------------
// The log event base class implementation
static void
write_data (const tl::Variant &data, std::ostream &os, unsigned int level)
{
if (data.is_list ()) {
for (unsigned int i = 0; i < level; ++i) {
os << " ";
}
os << "<block>" << std::endl;
for (tl::Variant::const_iterator b = data.begin (); b != data.end (); ++b) {
write_data (*b, os, level + 1);
}
for (unsigned int i = 0; i < level; ++i) {
os << " ";
}
os << "</block>" << std::endl;
} else {
for (unsigned int i = 0; i < level; ++i) {
os << " ";
}
if (data.is_long ()) {
os << "<int>" << data.to_long () << "</int>" << std::endl;
} else if (data.is_a_string ()) {
os << "<string>" << escape_string (data.to_string ()) << "</string>" << std::endl;
} else if (data.is_user<QImage> ()) {
QImage img (data.to_user<QImage> ());
QByteArray ba;
QBuffer buffer (&ba);
buffer.open (QIODevice::WriteOnly);
img.save (&buffer, "PNG");
os << "<img>" << ba.toBase64 ().constData () << "</img>" << std::endl;
}
}
}
void
LogEventBase::write (std::ostream &os, bool with_endl) const
{
std::vector< std::pair<std::string, std::string> > attrs;
attributes (attrs);
os << " <" << name ();
for (std::vector< std::pair<std::string, std::string> >::const_iterator a = attrs.begin (); a != attrs.end (); ++a) {
os << " " << a->first << "=\"" << a->second << "\"";
}
if (! data ().is_nil () && ! (data ().is_list () && data ().get_list ().empty ())) {
os << ">" << std::endl;
if (data ().is_list ()) {
for (tl::Variant::const_iterator b = data ().begin (); b != data ().end (); ++b) {
write_data (*b, os, 2);
}
} else {
write_data (data (), os, 2);
}
os << " </" << name () << ">";
} else {
os << "/>";
}
if (with_endl) {
os << std::endl;
}
}
// --------------------------------------------------------------
// The log event specializations
class LogTargetedEvent
: public LogEventBase
{
public:
LogTargetedEvent (const std::string &target, int xml_line = 0)
: LogEventBase (xml_line), m_target (target)
{
// .. nothing yet ..
}
const std::string &target () const
{
return m_target;
}
virtual void attributes (std::vector< std::pair<std::string, std::string> > &attr) const
{
attr.push_back (std::make_pair (std::string ("target"), m_target));
}
bool equals (const LogEventBase &b) const
{
const LogTargetedEvent *be = dynamic_cast <const LogTargetedEvent *> (&b);
if (! be) {
return false;
}
std::string name1, name2, cls1, cls2;
int nwidget1, nwidget2;
bool more1, more2;
tl::Extractor ex1 (m_target.c_str ());
tl::Extractor ex2 (be->m_target.c_str ());
do {
more1 = extract_widget_path (ex1, name1, cls1, nwidget1);
more2 = extract_widget_path (ex2, name2, cls2, nwidget2);
if (name1 != name2) {
return false;
}
// transition from non-class to class-based spec.:
if (cls1.empty () == cls2.empty ()) {
if (nwidget1 != nwidget2) {
return false;
}
}
} while (more1 && more2);
return !more1 && !more2;
}
QWidget *target_widget () const
{
return widget_from_path (m_target.c_str (), m_xml_line);
}
private:
std::string m_target;
};
class LogMouseEvent
: public LogTargetedEvent
{
public:
LogMouseEvent (const std::string &target, const QMouseEvent &me, int xml_line = 0)
: LogTargetedEvent (target, xml_line), m_mouse_event (me)
{
// .. nothing yet ..
}
virtual void issue_event ()
{
QWidget *target = target_widget ();
if (m_mouse_event.type () == QEvent::MouseButtonPress) {
target->setFocus ();
}
QMouseEvent me (m_mouse_event.type (), m_mouse_event.pos (), target->mapToGlobal (m_mouse_event.pos ()), m_mouse_event.button (), m_mouse_event.buttons (), m_mouse_event.modifiers ());
MouseTrackerWidget::instance ()->set (me);
Player::instance ()->issue_event (target, &me);
}
virtual const char *name () const
{
const char *event_name = "";
if (m_mouse_event.type () == QEvent::MouseMove) {
event_name = "mouse_move";
} else if (m_mouse_event.type() == QEvent::MouseButtonDblClick) {
event_name = "mouse_button_dbl_click";
} else if (m_mouse_event.type() == QEvent::MouseButtonPress) {
event_name = "mouse_button_press";
} else if (m_mouse_event.type() == QEvent::MouseButtonRelease) {
event_name = "mouse_button_release";
}
return event_name;
}
virtual void attributes (std::vector< std::pair<std::string, std::string> > &attr) const
{
LogTargetedEvent::attributes (attr);
attr.push_back (std::make_pair (std::string ("xpos"), tl::to_string (m_mouse_event.x ())));
attr.push_back (std::make_pair (std::string ("ypos"), tl::to_string (m_mouse_event.y ())));
if (m_mouse_event.type () == QEvent::MouseMove) {
attr.push_back (std::make_pair (std::string ("buttons"), tl::sprintf ("%x", int (m_mouse_event.buttons ()))));
} else {
attr.push_back (std::make_pair (std::string ("button"), tl::sprintf ("%x", int (m_mouse_event.button ()))));
}
attr.push_back (std::make_pair (std::string ("modifiers"), tl::sprintf ("%x", int (m_mouse_event.modifiers ()))));
}
const QMouseEvent &event () const
{
return m_mouse_event;
}
void move (const QPoint &p)
{
m_mouse_event = QMouseEvent (m_mouse_event.type (),
m_mouse_event.pos () + p,
m_mouse_event.globalPos () + p,
m_mouse_event.button (),
m_mouse_event.buttons (),
m_mouse_event.modifiers ());
}
bool equals (const LogEventBase &b) const
{
const LogMouseEvent *be = dynamic_cast <const LogMouseEvent *> (&b);
if (! be) {
return false;
}
return LogTargetedEvent::equals (b) &&
m_mouse_event.type () == be->m_mouse_event.type () &&
m_mouse_event.pos () == be->m_mouse_event.pos () &&
m_mouse_event.modifiers () == be->m_mouse_event.modifiers () &&
m_mouse_event.buttons () == be->m_mouse_event.buttons ();
}
private:
QMouseEvent m_mouse_event;
};
class LogKeyEvent
: public LogTargetedEvent
{
public:
LogKeyEvent (const std::string &target, const QKeyEvent &ke, int xml_line = 0)
: LogTargetedEvent (target, xml_line), m_key_event (ke)
{
// .. nothing yet ..
}
virtual void issue_event ()
{
QKeyEvent ke (m_key_event);
Player::instance ()->issue_event (target_widget (), &ke);
}
virtual const char *name () const
{
if (m_key_event.type() == QEvent::KeyPress) {
return "key_press";
} else {
return "key_release";
}
}
virtual void attributes (std::vector< std::pair<std::string, std::string> > &attr) const
{
LogTargetedEvent::attributes (attr);
QChar ch;
if (! m_key_event.text ().isEmpty ()) {
ch = m_key_event.text ().at (0);
}
attr.push_back (std::make_pair (std::string ("key"), tl::sprintf ("%x", int (m_key_event.key ()))));
attr.push_back (std::make_pair (std::string ("code"), tl::sprintf ("%x", int (ch.unicode ()))));
attr.push_back (std::make_pair (std::string ("modifiers"), tl::sprintf ("%x", int (m_key_event.modifiers ()))));
}
bool equals (const LogEventBase &b) const
{
const LogKeyEvent *be = dynamic_cast <const LogKeyEvent *> (&b);
if (! be) {
return false;
}
return LogTargetedEvent::equals (b) &&
m_key_event.modifiers () == be->m_key_event.modifiers () &&
m_key_event.key () == be->m_key_event.key ();
}
private:
QKeyEvent m_key_event;
};
class LogActionEvent
: public LogTargetedEvent
{
public:
LogActionEvent (const std::string &target, const std::string &action_name, int xml_line = 0)
: LogTargetedEvent (target, xml_line), m_action_name (action_name)
{
// .. nothing yet ..
}
virtual void issue_event ()
{
QList<QAction *> actions = target_widget ()->findChildren<QAction *> (tl::to_qstring (m_action_name));
if (actions.size () == 0) {
throw tl::Exception (tl::to_string (QObject::tr ("'%s' is not a valid action name (line %d)")), m_action_name, m_xml_line);
} else {
// trigger the specified action
(*(actions.begin ()))->trigger ();
}
}
virtual const char *name () const
{
return "action";
}
virtual void attributes (std::vector< std::pair<std::string, std::string> > &attr) const
{
LogTargetedEvent::attributes (attr);
attr.push_back (std::make_pair (std::string ("action"), m_action_name));
}
bool equals (const LogEventBase &b) const
{
const LogActionEvent *be = dynamic_cast <const LogActionEvent *> (&b);
if (! be) {
return false;
}
return LogTargetedEvent::equals (b) &&
m_action_name == be->m_action_name;
}
private:
std::string m_action_name;
};
class LogResizeEvent
: public LogTargetedEvent
{
public:
LogResizeEvent (const std::string &target, QSize size, QSize old_size, int xml_line = 0)
: LogTargetedEvent (target, xml_line), m_size (size), m_old_size (old_size)
{
// .. nothing yet ..
}
virtual void issue_event ()
{
target_widget ()->resize (m_size);
}
virtual const char *name () const
{
return "resize";
}
virtual void attributes (std::vector< std::pair<std::string, std::string> > &attr) const
{
LogTargetedEvent::attributes (attr);
attr.push_back (std::make_pair (std::string ("xsize"), tl::to_string (m_size.width ())));
attr.push_back (std::make_pair (std::string ("ysize"), tl::to_string (m_size.height ())));
attr.push_back (std::make_pair (std::string ("xsize_old"), tl::to_string (m_old_size.width ())));
attr.push_back (std::make_pair (std::string ("ysize_old"), tl::to_string (m_old_size.height ())));
}
QSize old_size () const
{
return m_old_size;
}
QSize size () const
{
return m_size;
}
bool equals (const LogEventBase &b) const
{
const LogResizeEvent *be = dynamic_cast <const LogResizeEvent *> (&b);
if (! be) {
return false;
}
return LogTargetedEvent::equals (b) &&
m_size == be->m_size &&
m_old_size == be->m_old_size;
}
virtual bool spontaneous () const
{
return true;
}
private:
QSize m_size;
QSize m_old_size;
};
class LogProbeEvent
: public LogTargetedEvent
{
public:
LogProbeEvent (const std::string &target, int xml_line)
: LogTargetedEvent (target, xml_line)
{
// .. nothing yet ..
}
LogProbeEvent (const std::string &target, const tl::Variant &d)
: LogTargetedEvent (target, 0)
{
set_data (d);
}
virtual void issue_event ()
{
if (gtf::Recorder::instance () && gtf::Recorder::instance ()->recording ()) {
QWidget *target = target_widget ();
QEvent event (QEvent::MaxUser);
event.ignore ();
Player::instance ()->issue_event (target, &event);
if (! event.isAccepted ()) {
gtf::Recorder::instance ()->probe (target, gtf::Recorder::instance ()->probe_std (target));
}
}
}
virtual const char *name () const
{
return "probe";
}
};
class LogErrorEvent
: public LogEventBase
{
public:
LogErrorEvent (int xml_line)
: LogEventBase (xml_line)
{
// .. for XML reader ..
}
LogErrorEvent (std::string &text)
: LogEventBase (0)
{
set_data (tl::Variant (text));
}
virtual void issue_event ()
{
// .. error events are not "issued" ..
}
virtual const char *name () const
{
return "error";
}
virtual void attributes (std::vector< std::pair<std::string, std::string> > & /*attr*/) const
{
// the error text is stored in the data
}
bool equals (const LogEventBase &b) const
{
return dynamic_cast <const LogErrorEvent *> (&b) != 0;
}
private:
std::string m_text;
};
// --------------------------------------------------------------
// Implementation of action_connect and action_disconnect
/**
* @brief A helper class to specify a connection for monitoring
*/
struct ConnectionSpec
{
public:
ConnectionSpec (QAction *sender, const char *signal)
: sender (sender), signal (signal)
{ }
bool operator== (const ConnectionSpec &d) const
{
return sender == d.sender && signal == d.signal;
}
bool operator< (const ConnectionSpec &d) const
{
if (sender != d.sender) {
return sender < d.sender;
}
if (signal != d.signal) {
return signal < d.signal;
}
return false;
}
QAction *sender;
std::string signal;
};
/**
* @brief A helper class to store ActionInterceptor objects associated with the connections monitored
*/
class ConnectionMap
{
public:
void register_connection (const ConnectionSpec &cs)
{
std::map<ConnectionSpec, std::pair<ActionInterceptor *, unsigned int> >::iterator i = m_map.find (cs);
if (i == m_map.end ()) {
ActionInterceptor *handler = new ActionInterceptor (cs.sender, cs.sender);
m_map.insert (std::make_pair (cs, std::make_pair (handler, (unsigned int) 1)));
QObject::connect (cs.sender, cs.signal.c_str (), handler, SLOT(triggered ()));
} else {
i->second.second++;
}
}
void unregister_connection (const ConnectionSpec &cs)
{
std::map<ConnectionSpec, std::pair<ActionInterceptor *, unsigned int> >::iterator i = m_map.find (cs);
tl_assert (i != m_map.end ());
tl_assert (i->second.second > 0);
if (--i->second.second == 0) {
QObject::disconnect (cs.sender, cs.signal.c_str (), i->second.first, SLOT(triggered ()));
delete i->second.first;
m_map.erase (i);
}
}
private:
std::map<ConnectionSpec, std::pair<ActionInterceptor *, unsigned int> > m_map;
};
// The monitored connections
static ConnectionMap s_action_map;
void
action_connect (QAction *action, const char *signal, QObject *receiver, const char *slot)
{
if (Recorder::instance ()) {
s_action_map.register_connection (ConnectionSpec (action, signal));
}
QObject::connect (action, signal, receiver, slot);
}
void
action_disconnect (QAction *action, const char *signal, QObject *receiver, const char *slot)
{
if (Recorder::instance ()) {
s_action_map.unregister_connection (ConnectionSpec (action, signal));
}
QObject::disconnect (action, signal, receiver, slot);
}
// --------------------------------------------------------------
// Implementation of ActionInterceptor
ActionInterceptor::ActionInterceptor (QObject *parent, QAction *action)
: QObject (parent), mp_action (action)
{
// .. nothing yet ..
}
void
ActionInterceptor::triggered ()
{
if (Recorder::instance ()) {
Recorder::instance ()->action (mp_action);
}
}
// --------------------------------------------------------------
// EventList implementation
EventList::EventList ()
{
// .. nothing yet ..
}
EventList::~EventList ()
{
for (std::vector<gtf::LogEventBase *>::const_iterator e = m_events.begin (); e != m_events.end (); ++e) {
delete *e;
}
m_events.clear ();
}
void
EventList::load (const std::string &filename, bool no_spontaneous)
{
QFile file (tl::to_qstring (filename));
if (! file.exists ()) {
throw tl::Exception (tl::to_string (QObject::tr ("File does not exist: ")) + filename);
}
QXmlInputSource source (&file);
GtfXmlHandler handler (this);
QXmlSimpleReader reader;
reader.setContentHandler (&handler);
reader.setErrorHandler (&handler);
reader.parse (&source, false /*=not incremental*/);
// remove spontaneous events if required
if (no_spontaneous) {
std::vector <LogEventBase *>::iterator ewrite = m_events.begin ();
std::vector <LogEventBase *>::iterator e = m_events.begin ();
while (e != m_events.end ()) {
if (! (*e)->spontaneous ()) {
*ewrite++ = *e;
} else {
delete *e;
}
++e;
}
m_events.erase (ewrite, m_events.end ());
}
}
void
EventList::save (const std::string &filename)
{
std::ostream *os;
if (filename != "-") {
os = new std::ofstream (filename.c_str ());
if (! os->good ()) {
delete os;
throw tl::Exception (tl::to_string (QObject::tr ("Unable to open file %s to write GUI test log")), filename);
}
} else {
os = &std::cout;
}
*os << "<testcase>" << std::endl;
for (const_iterator e = m_events.begin (); e != m_events.end (); ++e) {
(*e)->write (*os);
}
*os << "</testcase>" << std::endl;
if (os != &std::cout) {
delete os;
}
}
// --------------------------------------------------------------
// Recorder implementation
/**
* @brief A helper function telling if we shall log for the widget given
*
* This method defines which widgets not to log for. These include ToolBar widgets
* and Menu widgets as well as special ones used internally by Qt.
*/
static bool
is_valid_widget (QWidget *w)
{
if (dynamic_cast <QToolBar *> (w) != 0 ||
dynamic_cast <QMenuBar *> (w) != 0 ||
dynamic_cast <QMenu *> (w) != 0) {
return false;
} else if (w->parentWidget () == 0) {
return (dynamic_cast <QDialog *> (w) != 0 || dynamic_cast <QMainWindow *> (w) != 0);
} else {
return is_valid_widget (w->parentWidget ());
}
}
class ErrorLogRecorder
: public tl::Channel
{
public:
ErrorLogRecorder (Recorder *rec)
: mp_rec (rec)
{ }
protected:
virtual void puts (const char *s) { mp_rec->errlog_puts (s); }
virtual void endl () { mp_rec->errlog_endl (); }
virtual void end () { mp_rec->errlog_end (); }
virtual void begin () { mp_rec->errlog_begin (); }
private:
Recorder *mp_rec;
};
Recorder *Recorder::ms_instance = 0;
Recorder::Recorder (QObject *parent, const std::string &log_file)
: QObject (parent), m_recording (false), m_save_incremental (false), m_log_file (log_file)
{
// register the listener for error messages
mp_error_channel = new ErrorLogRecorder (this);
tl::error.add (mp_error_channel, false);
tl_assert (ms_instance == 0);
ms_instance = this;
}
Recorder::~Recorder ()
{
delete mp_error_channel;
mp_error_channel = 0;
stop ();
ms_instance = 0;
}
void
Recorder::start ()
{
tl_assert (! m_recording);
m_recording = true;
QCoreApplication *app = QCoreApplication::instance ();
app->installEventFilter (this);
}
void
Recorder::stop ()
{
if (m_recording) {
m_recording = false;
QCoreApplication *app = QCoreApplication::instance ();
app->removeEventFilter (this);
}
}
void
Recorder::action (QAction *action)
{
if (m_recording) {
QWidget *parent = dynamic_cast <QWidget *> (action->parent ());
tl_assert (parent != 0);
m_events.add (new LogActionEvent (widget_to_path (parent), tl::to_string (action->objectName ())));
}
}
void
Recorder::probe (QWidget *widget, const tl::Variant &data)
{
if (m_recording) {
m_events.add (new LogProbeEvent (widget_to_path (widget), data));
}
}
void
Recorder::errlog_begin ()
{
if (m_recording) {
m_error_text = "";
}
}
void
Recorder::errlog_end ()
{
if (m_recording) {
m_events.add (new LogErrorEvent (m_error_text));
}
}
void
Recorder::errlog_endl ()
{
if (m_recording) {
m_error_text += "\n";
}
}
void
Recorder::errlog_puts (const char *s)
{
if (m_recording) {
m_error_text += s;
}
}
void
Recorder::save_incremental (bool si)
{
m_save_incremental = si;
}
static tl::Variant
probe_widget (QTreeView *tv)
{
tl::Variant list = tl::Variant::empty_list ();
QAbstractItemModel *model = tv->model ();
int rows = model->rowCount ();
int columns = model->columnCount ();
if (rows > 0 && columns > 0) {
QModelIndex col0 (model->index (0, 0));
while (col0.isValid ()) {
int lvl = 0;
for (QModelIndex pindex = col0; pindex.isValid (); ++lvl) {
pindex = model->parent (pindex);
}
tl::Variant el = tl::Variant::empty_list ();
el.push (tl::Variant (long (lvl)));
el.push (tl::Variant ((tv->selectionModel ()->isSelected (col0)) ? "Selected" : "Not selected"));
for (int c = 0; c < columns; ++c) {
QModelIndex coln (col0.sibling (col0.row (), c));
QVariant font = model->data (coln, Qt::FontRole);
QVariant deco = model->data (coln, Qt::DecorationRole);
QVariant display = model->data (coln, Qt::DisplayRole);
if (! deco.value<QIcon> ().isNull ()) {
QImage img = deco.value<QIcon> ().pixmap (tv->iconSize ()).toImage ();
el.push (image_to_variant (img));
} else {
std::string t = tl::to_string (display.toString ());
t += " (";
QFont f (font.value<QFont> ());
bool first = true;
if (f.bold ()) {
t += "Bold";
first = false;
}
if (f.strikeOut ()) {
if (! first) {
t += ",";
}
t += "StrikeOut";
first = false;
}
if (f.italic ()) {
if (! first) {
t += ",";
}
t += "Italic";
first = false;
}
t += ")";
el.push (tl::Variant (t));
}
}
list.push (el);
col0 = tv->indexBelow (col0);
}
}
return list;
}
static tl::Variant
probe_widget (QTextEdit *te)
{
tl::Variant ret = tl::Variant::empty_list ();
QStringList lines = te->toPlainText ().split (QString::fromUtf8 ("\n"));
for (QStringList::const_iterator s = lines.begin (); s != lines.end (); ++s) {
ret.push (tl::to_string (*s));
}
return ret;
}
static tl::Variant
probe_widget (QLineEdit *le)
{
return tl::Variant (tl::to_string (le->text ()));
}
static tl::Variant
probe_widget (QSpinBox *sb)
{
return tl::Variant (long (sb->value ()));
}
static tl::Variant
probe_widget (QCheckBox *cb)
{
return tl::Variant (long (cb->isChecked ()));
}
static tl::Variant
probe_widget (QComboBox *cmb)
{
return tl::Variant (tl::to_string (cmb->lineEdit ()->text ()));
}
static tl::Variant
probe_widget (QListView *lv)
{
tl::Variant list = tl::Variant::empty_list ();
QAbstractItemModel *model = lv->model ();
int rows = model->rowCount ();
int columns = model->columnCount ();
if (rows > 0 && columns > 0) {
QModelIndex col0 (model->index (0, 0));
while (col0.isValid ()) {
tl::Variant el = tl::Variant::empty_list ();
el.push (tl::Variant ((lv->selectionModel ()->isSelected (col0)) ? "Selected" : "Not selected"));
for (int c = 0; c < columns; ++c) {
QModelIndex coln (col0.sibling (col0.row (), c));
QVariant font = model->data (coln, Qt::FontRole);
QVariant deco = model->data (coln, Qt::DecorationRole);
QVariant display = model->data (coln, Qt::DisplayRole);
if (! deco.value<QIcon> ().isNull ()) {
QImage img = deco.value<QIcon> ().pixmap (lv->iconSize ()).toImage ();
el.push (image_to_variant (img));
} else {
std::string t = tl::to_string (display.toString ());
t += " (";
QFont f (font.value<QFont> ());
bool first = true;
if (f.bold ()) {
t += "Bold";
first = false;
}
if (f.strikeOut ()) {
if (! first) {
t += ",";
}
t += "StrikeOut";
first = false;
}
if (f.italic ()) {
if (! first) {
t += ",";
}
t += "Italic";
first = false;
}
t += ")";
el.push (tl::Variant (t));
}
}
list.push (el);
col0 = col0.sibling (col0.row () + 1, 0);
}
}
return list;
}
static tl::Variant
probe_widget (QRadioButton *rb)
{
return tl::Variant (long (rb->isChecked ()));
}
static tl::Variant
probe_widget (QPushButton *pb)
{
if (! pb->icon ().isNull ()) {
return tl::Variant (image_to_variant (pb->icon ().pixmap (pb->iconSize ()).toImage ()));
} else {
return tl::Variant (tl::to_string (pb->text ()));
}
}
tl::Variant
Recorder::probe_std (QWidget *w)
{
if (dynamic_cast <QTreeView *> (w)) {
return probe_widget (dynamic_cast <QTreeView *> (w));
}
if (dynamic_cast <QLineEdit *> (w)) {
return probe_widget (dynamic_cast <QLineEdit *> (w));
}
if (dynamic_cast <QTextEdit *> (w)) {
return probe_widget (dynamic_cast <QTextEdit *> (w));
}
if (dynamic_cast <QSpinBox *> (w)) {
return probe_widget (dynamic_cast <QSpinBox *> (w));
}
if (dynamic_cast <QCheckBox *> (w)) {
return probe_widget (dynamic_cast <QCheckBox *> (w));
}
if (dynamic_cast <QComboBox *> (w)) {
return probe_widget (dynamic_cast <QComboBox *> (w));
}
if (dynamic_cast <QListView *> (w)) {
return probe_widget (dynamic_cast <QListView *> (w));
}
if (dynamic_cast <QRadioButton *> (w)) {
return probe_widget (dynamic_cast <QRadioButton *> (w));
}
if (dynamic_cast <QPushButton *> (w)) {
return probe_widget (dynamic_cast <QPushButton *> (w));
}
return tl::Variant ();
}
/*
* HINT: it was not a good idea to suppress mouse clicks to the context menu - this is caught by the
* system anyway (the release event is not received) and the additional event may be required to perform
* selects on cell trees for example.
static bool has_context_menu (QWidget *rec)
{
if (rec->contextMenuPolicy () == Qt::CustomContextMenu || rec->contextMenuPolicy () == Qt::ActionsContextMenu) {
return true;
} else if (rec->contextMenuPolicy () != Qt::PreventContextMenu && rec->parentWidget ()) {
// deferred to the parent
return has_context_menu (rec->parentWidget ());
}
return false;
}
*/
bool
Recorder::eventFilter (QObject *object, QEvent *event)
{
// do not handle events that are not targeted towards widgets
QWidget *rec = dynamic_cast <QWidget *> (object);
if (! rec) {
return false;
}
if (Player::instance () && Player::instance ()->playing ()) {
// handling in playing mode:
// suppress spontaneous input events in playing mode - the user may not interact
if (! Player::instance ()->event_issued () && dynamic_cast <QInputEvent *> (event) != 0) {
return true;
}
// only log events issued by the player
if (event->type () != QEvent::Resize &&
(Player::instance ()->event_issued () != event || Player::instance ()->event_target () != object)) {
return false;
}
} else {
// only log key events that are targeted towards widgets that do not have the focus
// this propagation of events is done automatically on replay in the same fashion.
if (dynamic_cast <QKeyEvent *> (event) != 0 && ! rec->hasFocus ()) {
return false;
}
// do not log propagation events for mouse events
if (dynamic_cast <QMouseEvent *> (event) != 0 && ! event->spontaneous ()) {
return false;
}
}
if (event->type() == QEvent::KeyPress || event->type () == QEvent::KeyRelease) {
QKeyEvent *keyEvent = dynamic_cast <QKeyEvent *> (event);
// Do not log Shift, Ctrl or Alt key events
if (keyEvent &&
keyEvent->key () != Qt::Key_Control &&
keyEvent->key () != Qt::Key_Alt &&
keyEvent->key () != Qt::Key_Shift &&
is_valid_widget (rec)) {
m_events.add (new LogKeyEvent (widget_to_path (rec), *keyEvent));
if (m_save_incremental) {
save ();
}
}
} else if (event->type() == QEvent::MouseButtonDblClick ||
event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = dynamic_cast <QMouseEvent *> (event);
// Pressing the mouse with Ctrl+Alt pressed issues a window probe
if (mouseEvent &&
(mouseEvent->button () & Qt::LeftButton) != 0 &&
(mouseEvent->modifiers () & Qt::ControlModifier) != 0 &&
(mouseEvent->modifiers () & Qt::AltModifier) != 0) {
if (event->type() == QEvent::MouseButtonPress) {
// send the test event to make the object print it's content
QEvent event (QEvent::MaxUser);
event.ignore ();
for (QWidget *w = rec; w != 0; w = w->parentWidget ()) {
QApplication::instance ()->sendEvent (w, &event);
if (event.isAccepted ()) {
tl::info << tl::to_string (QObject::tr ("Probed widget ")) << widget_to_path (w);
return true;
}
}
// if there is no special handling, try the default implementation
for (QWidget *w = rec; w != 0; w = w->parentWidget ()) {
tl::Variant p = probe_std (w);
if (! p.is_nil ()) {
probe (w, p);
tl::info << tl::to_string (QObject::tr ("Probed widget ")) << widget_to_path (w);
return true;
}
}
}
// eat probe events
return true;
/* HINT: see above (has_context_menu)
} else if (event->type() == QEvent::MouseButtonPress
&& (mouseEvent->button () & Qt::RightButton) != 0
&& has_context_menu (rec)) {
// suppress context menu events
*/
} else if (mouseEvent && is_valid_widget (rec)) {
m_events.add (new LogMouseEvent (widget_to_path (rec), *mouseEvent));
if (m_save_incremental) {
save ();
}
}
} else if (event->type() == QEvent::MouseMove) {
if (is_valid_widget (rec)) {
QMouseEvent *mouseEvent = dynamic_cast <QMouseEvent *> (event);
std::string wp = widget_to_path (rec);
// compress mouse events into a single one, if the buttons are the same
LogMouseEvent *me_log = 0;
if (mouseEvent &&
! m_events.empty () &&
(me_log = dynamic_cast <LogMouseEvent *> (m_events.back ())) != 0 &&
(me_log->event ().type () == QEvent::MouseMove &&
me_log->event ().buttons () == mouseEvent->buttons () &&
me_log->event ().button () == mouseEvent->button () &&
me_log->event ().modifiers () == mouseEvent->modifiers () &&
me_log->target () == wp)) {
// compress by adding the difference between the original and the
// current event. This avoids not taking into account widgets (like
// splitters) that move with the mouse and thus shift the reference
// coordinate system.
QPoint d = mouseEvent->globalPos () - me_log->event ().globalPos ();
me_log->move (d);
if (m_save_incremental) {
save ();
}
} else if (mouseEvent) {
m_events.add (new LogMouseEvent (wp, *mouseEvent));
if (m_save_incremental) {
save ();
}
}
}
} else if (event->type() == QEvent::Resize) {
// resize events are logged only for top-level widgets
QResizeEvent *resizeEvent = dynamic_cast <QResizeEvent *> (event);
if (resizeEvent && rec->parentWidget () == 0 && is_valid_widget (rec)) {
std::string target (widget_to_path (rec));
// compress resize events into a single event
LogResizeEvent *re_log = 0;
QSize old_size (resizeEvent->oldSize ());
if (! m_events.empty () &&
(re_log = dynamic_cast <LogResizeEvent *> (m_events.back ())) != 0) {
if (re_log->target () == target) {
old_size = re_log->old_size ();
delete m_events.back ();
m_events.pop_back ();
}
}
m_events.add (new LogResizeEvent (target, resizeEvent->size (), old_size));
if (m_save_incremental) {
save ();
}
}
}
return false;
}
// --------------------------------------------------------------
// Implementation of gtf::Player
Player *Player::ms_instance = 0;
Player::Player (QObject *parent)
: QObject (parent),
m_ms (0), m_playing_active (false), m_playing_index (0), m_breakpoint (-1), mp_event_issued (0), mp_event_target (0)
{
tl_assert (ms_instance == 0);
ms_instance = this;
mp_timer = new QTimer (this);
connect (mp_timer, SIGNAL (timeout ()), this, SLOT (timer ()));
}
Player::~Player ()
{
ms_instance = 0;
delete mp_timer;
mp_timer = 0;
}
void
Player::timer ()
{
if (m_playing_active && m_playing_index < m_events.size ()
&& (m_breakpoint < 0 || m_events [m_playing_index]->xml_line () <= m_breakpoint)) {
// restart timer (before issueing the event - because this might block in a modal dialog)
mp_timer->setSingleShot (true);
mp_timer->start (m_ms);
try {
if (tl::verbosity () >= 10) {
std::ostringstream info;
info.imbue (std::locale ("C"));
m_events [m_playing_index]->write (info, false /*no endl*/);
tl::info << m_events [m_playing_index]->xml_line () << ": " << info.str ();
}
m_events [m_playing_index++]->issue_event ();
} catch (tl::Exception &ex) {
tl::error << ex.msg ();
exit (1);
}
} else {
// done.
m_playing_active = false;
MouseTrackerWidget::instance ()->hide ();
}
}
void
Player::replay (int ms, int stop_at_line)
{
m_ms = ms;
m_playing_active = true;
m_breakpoint = stop_at_line;
mp_timer->setSingleShot (true);
mp_timer->start (0);
}
void
Player::issue_event (QWidget *target, QEvent *event)
{
mp_event_issued = event;
mp_event_target = target;
QApplication::sendEvent (target, event);
mp_event_issued = 0;
mp_event_target = 0;
}
// --------------------------------------------------------------
// Implementation of the XML handler
GtfXmlHandler::GtfXmlHandler (EventList *list)
: mp_locator (0),
mp_list (list),
m_in_event (false)
{
// .. nothing yet ..
}
void
GtfXmlHandler::setDocumentLocator (QXmlLocator *locator)
{
mp_locator = locator;
}
bool
GtfXmlHandler::characters (const QString &ch)
{
m_cdata += ch;
// continue
return true;
}
bool
GtfXmlHandler::endElement (const QString & /*namespaceURI*/, const QString &localName, const QString & /*qName*/)
{
if (localName == QString::fromUtf8 ("block")) {
tl_assert (m_data_stack.size () >= 2);
m_data_stack.end ()[-2].push (m_data_stack.end ()[-1]);
m_data_stack.pop_back ();
} else if (localName == QString::fromUtf8 ("string")) {
tl_assert (m_data_stack.size () >= 1);
m_data_stack.end ()[-1].push (tl::Variant (tl::to_string (m_cdata)));
} else if (localName == QString::fromUtf8 ("int")) {
long l = 0;
tl::from_string (tl::to_string (m_cdata), l);
tl_assert (m_data_stack.size () >= 1);
m_data_stack.end ()[-1].push (tl::Variant (l));
} else if (localName == QString::fromUtf8 ("img")) {
QByteArray ba (QByteArray::fromBase64 (m_cdata.toUtf8 ()));
QImage img;
img.loadFromData (ba);
tl_assert (m_data_stack.size () >= 1);
m_data_stack.end ()[-1].push (image_to_variant (img));
} else if (localName == QString::fromUtf8 ("mouse_button_release") ||
localName == QString::fromUtf8 ("mouse_button_press") ||
localName == QString::fromUtf8 ("mouse_button_dbl_click") ||
localName == QString::fromUtf8 ("mouse_move") ||
localName == QString::fromUtf8 ("key_press") ||
localName == QString::fromUtf8 ("key_release") ||
localName == QString::fromUtf8 ("action") ||
localName == QString::fromUtf8 ("resize") ||
localName == QString::fromUtf8 ("probe") ||
localName == QString::fromUtf8 ("error") ||
localName == QString::fromUtf8 ("block")) {
leave_event ();
}
// continue
return true;
}
bool
GtfXmlHandler::startElement (const QString & /*namespaceURI*/, const QString &localName, const QString & /*qName*/, const QXmlAttributes &atts)
{
if (localName == QString::fromUtf8 ("mouse_button_release") ||
localName == QString::fromUtf8 ("mouse_button_press") ||
localName == QString::fromUtf8 ("mouse_button_dbl_click")) {
int xpos = atts.value (QString::fromUtf8 ("xpos")).toInt ();
int ypos = atts.value (QString::fromUtf8 ("ypos")).toInt ();
int button = atts.value (QString::fromUtf8 ("button")).toInt (0, 16);
int modifiers = atts.value (QString::fromUtf8 ("modifiers")).toInt (0, 16);
QEvent::Type type;
if (localName == QString::fromUtf8 ("mouse_button_release")) {
type = QEvent::MouseButtonRelease;
} else if (localName == QString::fromUtf8 ("mouse_button_press")) {
type = QEvent::MouseButtonPress;
} else {
type = QEvent::MouseButtonDblClick;
}
QMouseEvent mouse_event (type, QPoint (xpos, ypos), Qt::MouseButton (button), Qt::MouseButtons (button), Qt::KeyboardModifiers (modifiers));
enter_event (new LogMouseEvent (tl::to_string (atts.value (QString::fromUtf8 ("target"))), mouse_event, mp_locator->lineNumber ()));
} else if (localName == QString::fromUtf8 ("mouse_move")) {
int xpos = atts.value (QString::fromUtf8 ("xpos")).toInt ();
int ypos = atts.value (QString::fromUtf8 ("ypos")).toInt ();
int buttons = atts.value (QString::fromUtf8 ("buttons")).toInt (0, 16);
int modifiers = atts.value (QString::fromUtf8 ("modifiers")).toInt (0, 16);
QMouseEvent mouse_event (QEvent::MouseMove, QPoint (xpos, ypos), Qt::MouseButton (Qt::NoButton), Qt::MouseButtons (buttons), Qt::KeyboardModifiers (modifiers));
enter_event (new LogMouseEvent (tl::to_string (atts.value (QString::fromUtf8 ("target"))), mouse_event, mp_locator->lineNumber ()));
} else if (localName == QString::fromUtf8 ("key_press") ||
localName == QString::fromUtf8 ("key_release")) {
int key = atts.value (QString::fromUtf8 ("key")).toInt (0, 16);
QChar text_char = QChar (atts.value (QString::fromUtf8 ("code")).toInt (0, 16));
QString text = text_char;
int modifiers = atts.value (QString::fromUtf8 ("modifiers")).toInt (0, 16);
QEvent::Type type;
if (localName == QString::fromUtf8 ("key_press")) {
type = QEvent::KeyPress;
} else {
type = QEvent::KeyRelease;
}
QKeyEvent key_event (type, Qt::Key (key), Qt::KeyboardModifiers (modifiers), text);
enter_event (new LogKeyEvent (tl::to_string (atts.value (QString::fromUtf8 ("target"))), key_event, mp_locator->lineNumber ()));
} else if (localName == QString::fromUtf8 ("action")) {
enter_event (new LogActionEvent (tl::to_string (atts.value (QString::fromUtf8 ("target"))), tl::to_string (atts.value (QString::fromUtf8 ("action"))), mp_locator->lineNumber ()));
} else if (localName == QString::fromUtf8 ("resize")) {
int xsize = atts.value (QString::fromUtf8 ("xsize")).toInt ();
int ysize = atts.value (QString::fromUtf8 ("ysize")).toInt ();
int xsize_old = atts.value (QString::fromUtf8 ("xsize_old")).toInt ();
int ysize_old = atts.value (QString::fromUtf8 ("ysize_old")).toInt ();
enter_event (new LogResizeEvent (tl::to_string (atts.value (QString::fromUtf8 ("target"))), QSize (xsize, ysize), QSize (xsize_old, ysize_old), mp_locator->lineNumber ()));
} else if (localName == QString::fromUtf8 ("probe")) {
enter_event (new LogProbeEvent (tl::to_string (atts.value (QString::fromUtf8 ("target"))), mp_locator->lineNumber ()));
} else if (localName == QString::fromUtf8 ("error")) {
enter_event (new LogErrorEvent (mp_locator->lineNumber ()));
} else if (localName == QString::fromUtf8 ("block")) {
m_data_stack.push_back (tl::Variant::empty_list ());
} else if (localName == QString::fromUtf8 ("string") ||
localName == QString::fromUtf8 ("int") ||
localName == QString::fromUtf8 ("img")) {
m_cdata = QString ();
}
// continue
return true;
}
void
GtfXmlHandler::enter_event (LogEventBase *event)
{
if (m_in_event) {
// TODO: (?) error ("Unexpected element");
return;
}
mp_list->add (event);
m_data_stack.clear ();
m_data_stack.push_back (tl::Variant::empty_list ());
m_in_event = true;
}
void
GtfXmlHandler::leave_event ()
{
if (! m_in_event) {
// TODO: (?) throw tl::XMLLocatedException (tl::to_string (QObject::tr ("Unexpected element")), ex.lineNumber (), ex.columnNumber ());
return;
}
mp_list->end ()[-1]->set_data (m_data_stack [0]);
m_data_stack.clear ();
m_in_event = false;
}
bool
GtfXmlHandler::error (const QXmlParseException &ex)
{
throw tl::XMLLocatedException (tl::to_string (ex.message ()), ex.lineNumber (), ex.columnNumber ());
}
bool
GtfXmlHandler::fatalError (const std::string &msg)
{
throw tl::XMLLocatedException (msg.c_str (), mp_locator->lineNumber (), mp_locator->columnNumber ());
}
bool
GtfXmlHandler::fatalError (const QXmlParseException &ex)
{
throw tl::XMLLocatedException (tl::to_string (ex.message ()), ex.lineNumber (), ex.columnNumber ());
}
bool
GtfXmlHandler::warning (const QXmlParseException &ex)
{
tl::XMLLocatedException lex (tl::to_string (ex.message ()), ex.lineNumber (), ex.columnNumber ());
tl::warn << lex.msg ();
// continue
return true;
}
}