Given that silly season is upon us and we're about to be subjected to a
drunken rampage through our precious code base, I thought I'd contribute
to the general loony toons by posting an implementation of LyX in only 500
lines.
Of course, it has rather restricted functionality (it responds only to
resize events), so it's not exactly very useful. Moreover, the "buffer" is
simply an image whose path has been input on the command line.
Nonetheless, it has exactly the same internal structure as the LyX GUI.
I wrote it in an attempt to track down Ekkehart Schlicht's report
(http://article.gmane.org/gmane.editors.lyx.devel:46711) that LyX/Win
turns an image scaled down to 10% of its size into a black rectangle. The
bad news is that this test case works perfectly (unlike LyX), both on
linux and on Windows, so it's not the test case that I was looking to
submit to the Qt/Win Free list. Don't know whether to smile or cry about
that one ;-)
Anyway, I post the source for those who have always been baffled by how LyX
does its stuff. Maybe it'll aid understanding. The key to this
understanding is the phrase "event driven" --- LyX responds to
resizeEvents, mouseClickEvents, etc from the OS). In this particular
instance, a resizeEvent causes "lyxlike" to paint it's simplified
"buffer".
However, I also post another version that uses QCanvas to achieve a similar
goal. Given the obvious reduction in complexity, maybe we should be using
something like that?
--
Angus
/**
* Compiled with
* g++ -I${QTDIR}/include -o canvas canvas.cpp -L${QTDIR}/lib -lqt-mt3
*
**/
#include <qapplication.h>
#include <qcanvas.h>
#include <qimage.h>
#include <qmainwindow.h>
#include <qpainter.h>
#include <qtextcodec.h>
#include <iostream>
#include <string>
namespace {
QString const toqstr(char const * str)
{
QTextCodec * const codec = QTextCodec::codecForLocale();
return codec->toUnicode(str);
}
QString const toqstr(std::string const & str)
{
return toqstr(str.c_str());
}
} // namespace anon
class ImageItem: public QCanvasRectangle
{
public:
ImageItem(QImage const & img, QCanvas & canvas)
: QCanvasRectangle(&canvas), image(img)
{
setSize( image.width(), image.height() );
pixmap = image;
}
protected:
void drawShape(QPainter &p)
{
p.drawPixmap(int(x()), int(y()), pixmap);
}
private:
QImage image;
QPixmap pixmap;
};
int main(int argc, char * argv[])
{
if (argc == 1) {
std::cerr << "Usage: " << argv[0] << " image_file\n";
return -1;
}
QApplication app(argc,argv);
char const * const filename = argv[1];
QImage image;
if (!image.load(toqstr(filename))) {
std::cerr << "Unable to open image '" << filename << "'\n";
return -1;
}
if (image.isNull()) {
std::cerr << "Image isNull! '" << filename << "'\n";
return -1;
}
QWMatrix m;
m.scale(0.1, 0.1);
image = image.xForm(m);
QCanvas canvas(int(1.2 * image.width()), int(1.2 * image.height()));
QMainWindow window;
window.setCaption("Rescaled image");
QCanvasView view(&canvas, &window, "canvas view", 0u);
window.setCentralWidget(&view);
ImageItem item(image, canvas);
item.move((canvas.width()-image.width()) / 2,
(canvas.height()-image.height()) / 2);
item.show();
window.show();
return app.exec();
}
/*
* Compiled with
*
* QTDIR="$HOME/qt3"
* BOOSTDIR="$HOME/lyx/13x/boost"
* BOOSTSIGNALSDIR="${HOME}/lyx/13x/build/boost/libs/signals/src/.libs"
*
* g++ -I"${QTDIR}"/include -I"${BOOSTDIR}" -o lyxlike lyxlike.cpp \
* -L"${QTDIR}"/lib -lqt-mt3 -L"${BOOSTSIGNALSDIR}" -lboostsignals
*/
#include <boost/bind.hpp>
#include <boost/signals/signal0.hpp>
#include <qapplication.h>
#include <qcanvas.h>
#include <qimage.h>
#include <qlayout.h>
#include <qmainwindow.h>
#include <qpainter.h>
#include <qtextcodec.h>
#include <cassert>
#include <exception>
#include <iostream>
#include <string>
namespace lyx {
QString const toqstr(char const * str)
{
QTextCodec * const codec = QTextCodec::codecForLocale();
return codec->toUnicode(str);
}
QString const toqstr(std::string const & str)
{
return toqstr(str.c_str());
}
class Buffer {
public:
Buffer(std::string const & image_filename);
QPixmap const & pixmap() const { return pixmap_; }
private:
QPixmap pixmap_;
std::string image_filename_;
};
class QtView;
class QScreen;
class QWorkArea;
class BufferView {
public:
BufferView(QtView *);
/// set the buffer we are viewing
void buffer(Buffer * b);
/// return the buffer being viewed
Buffer * buffer() const;
QtView * owner() const;
private:
QWorkArea & workarea() const;
void workAreaResize();
QtView * owner_;
Buffer * buffer_;
QWorkArea * workarea_;
QScreen * screen_;
};
class QtView : public QMainWindow {
public:
/// create a main window of the given dimensions
QtView(unsigned int w, unsigned int h);
void show();
BufferView * view() const { return bufferview_; }
private:
BufferView * bufferview_;
};
class QWorkArea;
class QLPainter {
public:
QLPainter(QWorkArea &);
virtual void start();
virtual void end();
virtual int paperWidth() const;
virtual int paperHeight() const;
virtual QLPainter & fillRectangle(int x, int y, int w, int h,
std::string const & name);
virtual QLPainter & image(int x, int y, int w, int h,
QPixmap const & p);
private:
QWorkArea & owner_;
QPainter qp_;
int paint_check_;
};
class QContentPane;
class QWorkArea : public QWidget {
public:
friend class QContentPane;
QWorkArea();
virtual ~QWorkArea() {}
virtual QLPainter & getPainter() { return painter_; }
QPixmap * getPixmap() const;
virtual int workWidth() const;
virtual int workHeight() const;
QWidget * getContent() const;
/// work area dimensions have changed
boost::signal0<void> workAreaResize;
private:
QContentPane * content_;
QLPainter painter_;
};
class QContentPane : public QWidget {
public:
QContentPane(QWorkArea * parent);
QPixmap * pixmap() const { return pixmap_; }
protected:
void resizeEvent(QResizeEvent * e);
/// repaint part of the widget
void paintEvent(QPaintEvent * e);
private:
QWorkArea * wa_;
QPixmap * pixmap_;
};
class QScreen {
public:
QScreen(QWorkArea &);
virtual ~QScreen();
//virtual void draw(LyXText *, BufferView *, unsigned int y);
virtual QWorkArea & workarea() const { return owner_; }
void repaint();
virtual void expose(int x, int y, int exp_width, int exp_height);
private:
/// our owning widget
QWorkArea & owner_;
};
Buffer::Buffer(std::string const & image_filename)
: image_filename_(image_filename)
{
std::cerr << "Buffer::Buffer(" << image_filename << ")\n";
QImage image;
if (!image.load(toqstr(image_filename))) {
std::cerr << "Unable to open image '"
<< image_filename << "'\n";
assert(false);
}
if (image.isNull()) {
std::cerr << "Image isNull! '"
<< image_filename << "'\n";
assert(false);
}
QWMatrix m;
m.scale(0.1, 0.1);
image = image.xForm(m);
pixmap_ = image;
std::cerr << "Leaving Buffer::Buffer()\n";
}
BufferView::BufferView(QtView * owner)
: owner_(owner),
buffer_(0),
workarea_(new QWorkArea),
screen_(new QScreen(*workarea_))
{
std::cerr << "BufferView::BufferView()\n";
workarea().workAreaResize
.connect(boost::bind(&BufferView::workAreaResize, this));
std::cerr << "Leaving BufferView::BufferView()\n";
}
void BufferView::buffer(Buffer * b)
{
std::cerr << "BufferView::buffer(" << b << ")\n";
buffer_ = b;
}
Buffer * BufferView::buffer() const
{
std::cerr << "BufferView::buffer()\n";
return buffer_;
}
QWorkArea & BufferView::workarea() const { return *workarea_; }
void BufferView::workAreaResize()
{
std::cerr << "BufferView::workAreaResize()\n";
static int work_area_width;
static int work_area_height;
bool const widthChange = workarea().workWidth() != work_area_width;
bool const heightChange = workarea().workHeight() != work_area_height;
// update from work area
work_area_width = workarea().workWidth();
work_area_height = workarea().workHeight();
if (buffer_ != 0 && (widthChange || heightChange)) {
QLPainter & painter = workarea().getPainter();
painter.start();
painter.fillRectangle(0, 0, work_area_width, work_area_height,
"linen");
QPixmap const & image = buffer_->pixmap();
int const x = 20;
int const y = 20;
int const w = image.width();
int const h = image.height();
painter.image(x, y, w, h, image);
painter.end();
screen_->expose(0, 0, work_area_width,work_area_height);
}
std::cerr << "Leaving BufferView::workAreaResize()\n";
}
QtView::QtView(unsigned int width, unsigned int height)
: QMainWindow(),
bufferview_(0)
{
std::cerr << "QtView::QtView(" << width << ',' << height << ")\n";
resize(width, height);
// Set the main widget before creating the BufferView.
qApp->setMainWidget(this);
bufferview_ = new BufferView(this);
std::cerr << "Leaving QtView::QtView()\n";
}
void QtView::show()
{
std::cerr << "QtView::show()\n";
setCaption(toqstr("Rescaled image"));
QMainWindow::show();
std::cerr << "Leaving QtView::show()\n";
}
QLPainter::QLPainter(QWorkArea & qwa)
: owner_(qwa), paint_check_(0)
{
std::cerr << "QLPainter::QLPainter()\n";
}
void QLPainter::start()
{
std::cerr << "QLPainter::start()\n";
if (++paint_check_ == 1)
qp_.begin(owner_.getPixmap());
std::cerr << "Leaving QLPainter::start()\n";
}
void QLPainter::end()
{
std::cerr << "QLPainter::end()\n";
if (paint_check_ == 0) {
std::cerr << "ended painting whilst not painting ??"
<< std::endl;
} else if (--paint_check_ == 0) {
qp_.end();
}
std::cerr << "Leaving QLPainter::end()\n";
}
int QLPainter::paperWidth() const
{
std::cerr << "QLPainter::paperWidth()\n";
return owner_.workWidth();
}
int QLPainter::paperHeight() const
{
std::cerr << "QLPainter::paperHeight()\n";
return owner_.workHeight();
}
QLPainter & QLPainter::fillRectangle(int x, int y, int w, int h,
std::string const & name)
{
std::cerr << "QLPainter::fillRectangle(" << x << ','
<< y << ',' << w << ',' << h << ',' << name << ")\n";
qp_.fillRect(x, y, w, h, QColor(toqstr(name)));
return *this;
}
QLPainter & QLPainter::image(int x, int y, int w, int h, QPixmap const & p)
{
std::cerr << "QLPainter::image()\n";
qp_.drawPixmap(x, y, p, 0, 0, w, h);
return *this;
}
QWorkArea::QWorkArea()
: QWidget(qApp->mainWidget()),
content_(new QContentPane(this)),
painter_(*this)
{
std::cerr << "QWorkArea::QWorkArea()\n";
(static_cast<QMainWindow*>(qApp->mainWidget()))->setCentralWidget(this);
content_->show();
content_->setBackgroundColor(toqstr("linen"));
QHBoxLayout * vl = new QHBoxLayout(this);
vl->addWidget(content_, 5);
show();
std::cerr << "Leaving QWorkArea::QWorkArea()\n";
}
QPixmap * QWorkArea::getPixmap() const
{
std::cerr << "QWorkArea::getPixmap()\n";
return content_->pixmap();
}
int QWorkArea::workWidth() const
{
std::cerr << "QWorkArea::workWidth()\n";
return content_->width();
}
int QWorkArea::workHeight() const
{
std::cerr << "QWorkArea::workHeight()\n";
return content_->height();
}
QWidget * QWorkArea::getContent() const
{
std::cerr << "QWorkArea::getContent()\n";
return content_;
}
QContentPane::QContentPane(QWorkArea * parent)
: QWidget(parent, "content_pane", WRepaintNoErase),
wa_(parent),
pixmap_(0)
{
std::cerr << "QContentPane::QContentPane()\n";
setFocusPolicy(QWidget::WheelFocus);
setFocus();
setCursor(ibeamCursor);
std::cerr << "Leaving QContentPane::QContentPane()\n";
}
void QContentPane::resizeEvent(QResizeEvent *)
{
std::cerr << "QContentPane::resizeEvent(): "
<< "width " << width() << " height " << height() << '\n';
if (!pixmap_) {
pixmap_ = new QPixmap(width(), height());
}
pixmap_->resize(width(), height());
wa_->workAreaResize();
std::cerr << "Leaving QContentPane::resizeEvent()\n";
}
void QContentPane::paintEvent(QPaintEvent * e)
{
std::cerr << "QContentPane::paintEvent(): "
<< "width " << width() << " height " << height() << '\n';
if (!pixmap_) {
pixmap_ = new QPixmap(width(), height());
std::cerr << "Leaving QContentPane::paintEvent() early\n";
return;
}
QRect r(e->rect());
QPainter q(this);
q.drawPixmap(QPoint(r.x(), r.y()), *pixmap_, r);
std::cerr << "Leaving QContentPane::paintEvent()\n";
}
QScreen::QScreen(QWorkArea & o)
: owner_(o)
{
std::cerr << "QScreen::QScreen()\n";
}
QScreen::~QScreen()
{
std::cerr << "QScreen::~QScreen()\n";
}
void QScreen::repaint()
{
std::cerr << "QScreen::repaint()\n";
QWidget * content(owner_.getContent());
content->repaint(0, 0, content->width(), content->height());
std::cerr << "Leaving QScreen::repaint()\n";
}
void QScreen::expose(int x, int y, int w, int h)
{
std::cerr << "QScreen::expose " << w << 'x' << h
<< '+' << x << '+' << y << std::endl;
owner_.getContent()->update(x, y, w, h);
std::cerr << "Leaving QScreen::expose()\n";
}
} // namespace lyx
int main(int argc, char * argv[])
{
if (argc == 1) {
std::cerr << "Usage: " << argv[0] << " image_file\n";
return -1;
}
QApplication app(argc,argv);
char const * const filename = argv[1];
lyx::Buffer * buffer = new lyx::Buffer(filename);
unsigned int width = 690;
unsigned int height = 510;
lyx::QtView view(width, height);
view.view()->buffer(buffer);
view.show();
return app.exec();
}
namespace boost {
void throw_exception(std::exception const & e)
{
std::cerr << "Exception caught:\n" << e.what() << std::endl;
assert(false);
}
} // namespace boost