Qt: widget/gfx work (gfx part)
authorvlad
Sun, 20 Apr 2008 11:27:17 +0300
changeset 16886 840d18be6075e2be1fa8d1b6f11f3b90fbaf2541
parent 16885 8f68aab12dfbe97219e6019c998cdd53d2550951
child 16887 f41143848d2b43bb3d61d70f6558af6c7680f95c
push idunknown
push userunknown
push dateunknown
milestone2.0a1pre
Qt: widget/gfx work (gfx part) - Fix crash on window close - Qt expects the QWidget to remain available even after it has processed a close event. So, use deleteLater() instead of deleting outright. - Also carry around the MozQWidget pointer (though we need to rework CreateQWidget and ::Initialize) so that we can call dropReceiver - Fix screen <-> widget coordinate translation - Fixes popup menu positioning - Honor mayWait in nsAppShell::ProcessNextNativeEvent - Prevents massive busy-waiting - Don't pretend to support translucent windows - Fix embedding widget build failure - cairo-qpainter: invert source matrix in paint() to get proper transform (proper positioning of elements drawn using paint()) - Cleaner event handling in mozqwidget -- return full nsEventStatus, not just boolean, and decide whether to call QWidget::event() based on that.
gfx/cairo/cairo/src/cairo-qpainter-surface.cpp
--- a/gfx/cairo/cairo/src/cairo-qpainter-surface.cpp
+++ b/gfx/cairo/cairo/src/cairo-qpainter-surface.cpp
@@ -32,74 +32,112 @@
  *
  * Contributor(s):
  *      Vladimir Vukicevic <vladimir@mozilla.com>
  */
 
 /* Get INT16_MIN etc. as per C99 */
 #define __STDC_LIMIT_MACROS
 
+#include "cairoint.h"
+
+#include "cairo-qpainter.h"
+
 #include <QtGui/QPainter>
 #include <QtGui/QPaintEngine>
 #include <QtGui/QPaintDevice>
 #include <QtGui/QImage>
 #include <QtGui/QPixmap>
 #include <QtGui/QBrush>
 #include <QtGui/QPen>
 #include <QtGui/QWidget>
 #include <QtGui/QX11Info>
 
-#include "cairoint.h"
-
-#include "cairo-qpainter.h"
-
 #ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
 #include "cairo-xlib.h"
 #include "cairo-xlib-xrender.h"
 // I hate X
 #undef Status
 #undef CursorShape
+#undef Bool
 #endif
 
+#include <sys/time.h>
+
 #if 0
 #define D(x)  x
 #else
 #define D(x) do { } while(0)
 #endif
 
+#if 0
+#define DP(x) if (g_dump_path) { x; }
+#else
+#define DP(x) do { } while(0)
+#endif
+
 #ifndef CAIRO_INT_STATUS_SUCCESS
 #define CAIRO_INT_STATUS_SUCCESS ((cairo_int_status_t) CAIRO_STATUS_SUCCESS)
 #endif
 
 typedef struct {
     cairo_surface_t base;
 
     QPainter *p;
 
     /* The pixmap/image constructors will store their objects here */
     QPixmap *pixmap;
     QImage *image;
 
     QRect window;
+    QRegion *clip_region;
 
     cairo_image_surface_t *image_equiv;
 
 #if defined(Q_WS_X11) && defined(CAIRO_HAS_XLIB_XRENDER_SURFACE)
     /* temporary, so that we can share the xlib surface's glyphs code */
     cairo_surface_t *xlib_equiv;
 #endif
 
     cairo_bool_t supports_porter_duff;
 } cairo_qpainter_surface_t;
 
+/* Will be set to TRUE if we ever try to create a QPixmap and end
+ * up with one without an alpha channel.
+ */
+static cairo_bool_t _qpixmaps_have_no_alpha = FALSE;
+
 static cairo_int_status_t
 _cairo_qpainter_surface_paint (void *abstract_surface,
                                cairo_operator_t op,
                                cairo_pattern_t *source);
 
+/* some debug timing stuff */
+static int g_dump_path = 0;
+static struct timeval timer_start_val;
+#if 0
+static void tstart() {
+    gettimeofday(&timer_start_val, NULL);
+}
+
+static void tend(const char *what = NULL) {
+    struct timeval timer_stop;
+    gettimeofday(&timer_stop, NULL);
+
+    double ms_start = timer_start_val.tv_sec * 1000.0 + timer_start_val.tv_usec / 1000.0;
+    double ms_end = timer_stop.tv_sec * 1000.0 + timer_stop.tv_usec / 1000.0;
+
+    if (ms_end - ms_start > 1.0)
+	fprintf (stderr, "******* %s elapsed: %f ms\n", what ? what : "(timer)", ms_end - ms_start);
+}
+#else
+static void tstart() { }
+static void tend(const char *what = NULL) { }
+#endif
+
 /**
  ** Helper methods
  **/
 static const char *
 _opstr (cairo_operator_t op)
 {
     const char *ops[] = {
         "CLEAR",
@@ -232,16 +270,18 @@ static cairo_status_t
 {
     qpainter_path_data *pdata = (qpainter_path_data *)closure;
     double x = _cairo_fixed_to_double (point->x);
     double y = _cairo_fixed_to_double (point->y);
 
     if (pdata->ctm_inverse)
         cairo_matrix_transform_point (pdata->ctm_inverse, &x, &y);
 
+    DP(fprintf(stderr, "moveto %4.2f %4.2f\n", x, y));
+
     pdata->path->moveTo(x, y);
 
     return CAIRO_STATUS_SUCCESS;
 }
 
 static cairo_status_t
 _cairo_path_to_qpainterpath_line_to (void *closure, cairo_point_t *point)
 {
@@ -254,16 +294,18 @@ static cairo_status_t
 
     pdata->path->lineTo(x, y);
 
     if (pdata->path->isEmpty())
         pdata->path->moveTo(x, y);
     else
         pdata->path->lineTo(x, y);
 
+    DP(fprintf(stderr, "lineto %4.2f %4.2f\n", x, y));
+
     return CAIRO_STATUS_SUCCESS;
 }
 
 static cairo_status_t
 _cairo_path_to_qpainterpath_curve_to (void *closure, cairo_point_t *p0, cairo_point_t *p1, cairo_point_t *p2)
 {
     qpainter_path_data *pdata = (qpainter_path_data *)closure;
     double x0 = _cairo_fixed_to_double (p0->x);
@@ -276,26 +318,30 @@ static cairo_status_t
     if (pdata->ctm_inverse) {
         cairo_matrix_transform_point (pdata->ctm_inverse, &x0, &y0);
         cairo_matrix_transform_point (pdata->ctm_inverse, &x1, &y1);
         cairo_matrix_transform_point (pdata->ctm_inverse, &x2, &y2);
     }
 
     pdata->path->cubicTo (x0, y0, x1, y1, x2, y2);
 
+    DP(fprintf(stderr, "curveto %f %f %f %f %f %f\n", x0, y0, x1, y1, x2, y2));
+
     return CAIRO_STATUS_SUCCESS;
 }
 
 static cairo_status_t
 _cairo_path_to_qpainterpath_close_path (void *closure)
 {
     qpainter_path_data *pdata = (qpainter_path_data *)closure;
 
     pdata->path->closeSubpath();
 
+    DP(fprintf(stderr, "closepath\n"));
+
     return CAIRO_STATUS_SUCCESS;
 }
 
 static cairo_status_t
 _cairo_quartz_cairo_path_to_qpainterpath (cairo_path_fixed_t *path,
                                           QPainterPath *qpath,
                                           cairo_fill_rule_t fill_rule,
                                           cairo_matrix_t *ctm_inverse = NULL)
@@ -329,26 +375,37 @@ static cairo_status_t
 static cairo_surface_t *
 _cairo_qpainter_surface_create_similar (void *abstract_surface,
                                         cairo_content_t content,
                                         int width,
                                         int height)
 {
     cairo_qpainter_surface_t *qs = (cairo_qpainter_surface_t *) abstract_surface;
 
+    cairo_bool_t do_image = qs->image != NULL;
+
     D(fprintf(stderr, "q[%p] create_similar: %d %d [%d] -> ", abstract_surface, width, height, content));
 
-    if (qs->image || content == CAIRO_CONTENT_COLOR_ALPHA) {
-        fprintf (stderr, "qimage\n");
-        return cairo_qpainter_surface_create_with_qimage
-               (_cairo_format_from_content (content), width, height);
+    if (!do_image && (!_qpixmaps_have_no_alpha || content == CAIRO_CONTENT_COLOR)) {
+	cairo_surface_t *result =
+	    cairo_qpainter_surface_create_with_qpixmap (width, height);
+
+	if (cairo_surface_get_content (result) == content) {
+	    D(fprintf(stderr, "qpixmap\n"));
+	    return result;
+	}
+
+	fprintf (stderr, " pixmaps have no alpha!\n");
+	_qpixmaps_have_no_alpha = TRUE;
+	cairo_surface_destroy (result);
     }
 
-    D(fprintf(stderr, "qpixmap\n"));
-    return cairo_qpainter_surface_create_with_qpixmap (width, height);
+    D(fprintf (stderr, "qimage\n"));
+    return cairo_qpainter_surface_create_with_qimage
+	(_cairo_format_from_content (content), width, height);
 }
 
 static cairo_status_t
 _cairo_qpainter_surface_finish (void *abstract_surface)
 {
     cairo_qpainter_surface_t *qs = (cairo_qpainter_surface_t *) abstract_surface;
 
     D(fprintf(stderr, "q[%p] finish\n", abstract_surface));
@@ -454,16 +511,19 @@ static cairo_status_t
     }
 
     if (qs->pixmap) {
         qimg = new QImage(qs->pixmap->toImage());
     } else {
         // Try to figure out what kind of QPaintDevice we have, and
         // how we can grab an image from it
         QPaintDevice *pd = qs->p->device();
+	if (!pd)
+	    return CAIRO_STATUS_NO_MEMORY;
+
         if (pd->devType() == QInternal::Image) {
             qimg = new QImage(((QImage*) pd)->copy());
         } else if (pd->devType() == QInternal::Pixmap) {
             qimg = new QImage(((QPixmap*) pd)->toImage());
         } else if (pd->devType() == QInternal::Widget) {
             qimg = new QImage(QPixmap::grabWindow(((QWidget*)pd)->winId()).toImage());
         }
     }
@@ -519,24 +579,31 @@ static cairo_status_t
                                        cairo_surface_t *src,
                                        int              src_x,
                                        int              src_y,
                                        int              width,
                                        int              height,
                                        cairo_surface_t **clone_out)
 {
     cairo_qpainter_surface_t *qs = (cairo_qpainter_surface_t *) abstract_surface;
-    cairo_surface_t *new_surf;
+    cairo_surface_t *new_surf = NULL;
 
-    // For non-image targets, and a CONTENT_COLOR type, use a QPixmap
-    // Otherwise, create a QImage
-    if (qs->image == NULL && src->content == CAIRO_CONTENT_COLOR) {
+    // For non-image targets, always try to create a QPixmap first
+    if (qs->image == NULL && (!_qpixmaps_have_no_alpha || src->content == CAIRO_CONTENT_COLOR))
+    {
         new_surf = cairo_qpainter_surface_create_with_qpixmap
                    (width, height);
-    } else {
+	if (cairo_surface_get_content (new_surf) != src->content) {
+	    cairo_surface_destroy (new_surf);
+	    _qpixmaps_have_no_alpha = TRUE;
+	    new_surf = NULL;
+	}
+    }
+
+    if (new_surf == NULL) {
         new_surf = cairo_qpainter_surface_create_with_qimage
                    (_cairo_format_from_content (src->content), width, height);
     }
 
     if (new_surf->status)
         return new_surf->status;
 
     cairo_pattern_union_t upat;
@@ -584,40 +651,111 @@ static cairo_int_status_t
     cairo_qpainter_surface_t *qs = (cairo_qpainter_surface_t *) abstract_surface;
 
     D(fprintf(stderr, "q[%p] intersect_clip_path %s\n", abstract_surface, path ? "(path)" : "(clear)"));
 
     if (!qs->p)
         return CAIRO_INT_STATUS_SUCCESS;
 
     if (path == NULL) {
+	//fprintf (stderr, "clip clear\n");
         // How the clip path is reset depends on whether we own p or not
         if (qs->pixmap || qs->image) {
             // we own p
             qs->p->setClipping (false);
         } else {
             qs->p->restore ();
             qs->p->save ();
         }
 
         return CAIRO_INT_STATUS_SUCCESS;
     }
 
-    Qt::ClipOperation clipOp = Qt::IntersectClip;
-    if ((qs->pixmap || qs->image) && !qs->p->hasClipping())
-        clipOp = Qt::ReplaceClip;
+    // Qt will implicity enable clipping, and will use ReplaceClip
+    // instead of IntersectClip if clipping was disabled before
+
+    // Note: Qt is really bad at dealing with clip paths.  It doesn't
+    // seem to usefully recognize rectangular paths, instead going down
+    // extremely slow paths whenever a clip path is set.  So,
+    // we do a bunch of work here to try to get rectangles or regions
+    // down to Qt for clipping.
+
+    tstart();
+
+    // First check if it's an integer-aligned single rectangle
+    cairo_box_t box;
+    if (_cairo_path_fixed_is_box (path, &box) &&
+	_cairo_fixed_is_integer (box.p1.x) &&
+	_cairo_fixed_is_integer (box.p1.y) &&
+	_cairo_fixed_is_integer (box.p2.x) &&
+	_cairo_fixed_is_integer (box.p2.y))
+    {
+	QRect r(_cairo_fixed_integer_part(box.p1.x),
+		_cairo_fixed_integer_part(box.p1.y),
+		_cairo_fixed_integer_part(box.p2.x - box.p1.x),
+		_cairo_fixed_integer_part(box.p2.y - box.p1.y));
+
+	r = r.normalized();
+
+	//fprintf (stderr, "clip rect: %d %d %d %d\n", r.x(), r.y(), r.width(), r.height());
+	qs->p->setClipRect (r, Qt::IntersectClip);
+
+    } else {
+	// Then if it's not an integer-aligned rectangle, check
+	// if we can extract a region (a set of rectangles) out.
+	// We use cairo to convert the path to traps.
+
+	cairo_traps_t traps;
+	cairo_int_status_t status;
+	cairo_region_t region;
 
-    QPainterPath qpath;
-    if (_cairo_quartz_cairo_path_to_qpainterpath (path, &qpath, fill_rule) != CAIRO_STATUS_SUCCESS)
-        return CAIRO_INT_STATUS_UNSUPPORTED;
+	_cairo_traps_init (&traps);
+	status = (cairo_int_status_t)
+	    _cairo_path_fixed_fill_to_traps (path, fill_rule, tolerance, &traps);
+	if (status) {
+	    _cairo_traps_fini (&traps);
+	    return status;
+	}
+
+	status = _cairo_traps_extract_region (&traps, &region);
+	_cairo_traps_fini (&traps);
+
+	if (status == CAIRO_STATUS_SUCCESS) {
+	    cairo_box_int_t *boxes;
+	    int n_boxes;
+
+	    QRegion qr;
+
+	    _cairo_region_get_boxes (&region, &n_boxes, &boxes);
 
-    qs->p->setClipPath (qpath, clipOp);
-    qs->p->setClipping (true);
+	    for (int i = 0; i < n_boxes; i++) {
+		qr = qr.unite(QRect(boxes[i].p1.x,
+				    boxes[i].p1.y,
+				    boxes[i].p2.x - boxes[i].p1.x,
+				    boxes[i].p2.y - boxes[i].p1.y));
+	    }
+
+	    _cairo_region_boxes_fini (&region, boxes);
+	    _cairo_region_fini (&region);
 
-    // XXX Antialiasing is ignored
+	    qs->p->setClipRegion (qr, Qt::IntersectClip);
+	} else {
+	    // We weren't able to extract a region from the traps.
+	    // Just hand the path down to QPainter.
+	    QPainterPath qpath;
+
+	    if (_cairo_quartz_cairo_path_to_qpainterpath (path, &qpath, fill_rule) != CAIRO_STATUS_SUCCESS)
+		return CAIRO_INT_STATUS_UNSUPPORTED;
+
+	    // XXX Antialiasing is ignored
+	    qs->p->setClipPath (qpath, Qt::IntersectClip);
+	}
+    }
+
+    tend("clip");
 
     return CAIRO_INT_STATUS_SUCCESS;
 }
 
 /**
  ** Brush conversion
  **/
 
@@ -711,20 +849,36 @@ QPen *
         join = Qt::RoundJoin;
         break;
     case CAIRO_LINE_JOIN_BEVEL:
         join = Qt::BevelJoin;
         break;
     }
 
     pen = new QPen (*brush, style->line_width, Qt::SolidLine, cap, join);
+    pen->setMiterLimit (style->miter_limit);
 
-    // XXX handle style->dash!
+    if (style->dash && style->num_dashes) {
+	unsigned int odd_dash = style->num_dashes % 2;
 
-    // I hope that the QPen made a copy...
+	QVector<qreal> dashes (odd_dash ? style->num_dashes * 2 : style->num_dashes);
+	for (unsigned int i = 0; i < odd_dash+1; i++) {
+	    for (unsigned int j = 0; j < style->num_dashes; j++) {
+		// In Qt, the dash lengths are given in units of line width, whereas
+		// in cairo, they are in user-space units.  We'll always apply the CTM,
+		// so all we have to do here is divide cairo's dash lengths by the line
+		// width.
+		dashes.append (style->dash[j] / style->line_width);
+	    }
+	}
+
+	pen->setDashPattern (dashes);
+	pen->setDashOffset (style->dash_offset / style->line_width);
+    }
+
     delete brush;
 
     return pen;
 }
 
 /**
  ** Core drawing operations
  **/
@@ -746,17 +900,19 @@ cairo_int_status_t
 
     if (source->type == CAIRO_PATTERN_TYPE_SURFACE &&
             source->extend == CAIRO_EXTEND_NONE &&
             ((cairo_surface_pattern_t*)source)->surface->type == CAIRO_SURFACE_TYPE_QPAINTER)
     {
         cairo_qpainter_surface_t *qsSrc = (cairo_qpainter_surface_t*) ((cairo_surface_pattern_t*)source)->surface;
 
         QMatrix savedMatrix = qs->p->worldMatrix();
-        qs->p->setWorldMatrix (_qmatrix_from_cairo_matrix (source->matrix), true);
+	cairo_matrix_t m = source->matrix;
+	cairo_matrix_invert (&m);
+        qs->p->setWorldMatrix (_qmatrix_from_cairo_matrix (m), true);
 
         if (qsSrc->image) {
             qs->p->drawImage (0, 0, *qsSrc->image);
         } else if (qsSrc->pixmap) {
             qs->p->drawPixmap (0, 0, *qsSrc->pixmap);
         }
 
         qs->p->setWorldMatrix (savedMatrix, false);
@@ -785,35 +941,41 @@ static cairo_int_status_t
 {
     cairo_qpainter_surface_t *qs = (cairo_qpainter_surface_t *) abstract_surface;
 
     D(fprintf(stderr, "q[%p] fill op:%s\n", abstract_surface, _opstr(op)));
 
     if (!qs->p)
         return CAIRO_INT_STATUS_SUCCESS;
 
+    tstart();
+
     QPainterPath qpath;
     if (_cairo_quartz_cairo_path_to_qpainterpath (path, &qpath, fill_rule) != CAIRO_STATUS_SUCCESS)
         return CAIRO_INT_STATUS_UNSUPPORTED;
 
     if (qs->supports_porter_duff)
         qs->p->setCompositionMode (_qpainter_compositionmode_from_cairo_op (op));
 
-    qs->p->setRenderHint (QPainter::Antialiasing, antialias == CAIRO_ANTIALIAS_NONE ? false : true);
+    // XXX Qt4.3, 4.4 misrenders some complex paths if antialiasing is
+    // enabled
+    //qs->p->setRenderHint (QPainter::Antialiasing, antialias == CAIRO_ANTIALIAS_NONE ? false : true);
     qs->p->setRenderHint (QPainter::SmoothPixmapTransform, source->filter != CAIRO_FILTER_FAST);
 
     QBrush *brush = _qbrush_from_pattern (source);
 
     qs->p->fillPath (qpath, *brush);
 
     delete brush;
 
     if (qs->supports_porter_duff)
         qs->p->setCompositionMode (QPainter::CompositionMode_SourceOver);
 
+    tend("fill");
+
     return CAIRO_INT_STATUS_SUCCESS;
 }
 
 static cairo_int_status_t
 _cairo_qpainter_surface_stroke (void *abstract_surface,
                                 cairo_operator_t op,
                                 cairo_pattern_t *source,
                                 cairo_path_fixed_t *path,
@@ -835,17 +997,19 @@ static cairo_int_status_t
         return CAIRO_INT_STATUS_UNSUPPORTED;
 
     QMatrix savedMatrix = qs->p->worldMatrix();
 
     if (qs->supports_porter_duff)
         qs->p->setCompositionMode (_qpainter_compositionmode_from_cairo_op (op));
 
     qs->p->setWorldMatrix (_qmatrix_from_cairo_matrix (*ctm), true);
-    qs->p->setRenderHint (QPainter::Antialiasing, antialias == CAIRO_ANTIALIAS_NONE ? false : true);
+    // XXX Qt4.3, 4.4 misrenders some complex paths if antialiasing is
+    // enabled
+    //qs->p->setRenderHint (QPainter::Antialiasing, antialias == CAIRO_ANTIALIAS_NONE ? false : true);
     qs->p->setRenderHint (QPainter::SmoothPixmapTransform, source->filter != CAIRO_FILTER_FAST);
 
     QPen *pen = _qpen_from_pattern_and_style (source, style);
 
     qs->p->setPen(*pen);
     qs->p->drawPath (qpath);
     qs->p->setPen(Qt::black);
 
@@ -1062,17 +1226,20 @@ cairo_qpainter_surface_create (QPainter 
     if (qs == NULL)
         return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
 
     memset (qs, 0, sizeof(cairo_qpainter_surface_t));
 
     _cairo_surface_init (&qs->base, &cairo_qpainter_surface_backend, CAIRO_CONTENT_COLOR_ALPHA);
 
     qs->p = painter;
-    qs->supports_porter_duff = qs->p->paintEngine()->hasFeature(QPaintEngine::PorterDuff);
+    if (qs->p->paintEngine())
+	qs->supports_porter_duff = qs->p->paintEngine()->hasFeature(QPaintEngine::PorterDuff);
+    else
+	qs->supports_porter_duff = FALSE;
 
     // Save so that we can always get back to the original state
     qs->p->save();
 
     qs->window = painter->window();
 
     D(fprintf(stderr, "qpainter_surface_create: window: [%d %d %d %d] pd:%d\n",
               qs->window.x(), qs->window.y(), qs->window.width(), qs->window.height(),
@@ -1186,18 +1353,16 @@ cairo_qpainter_surface_get_qpainter (cai
  * TODO:
  *
  * - Figure out why QBrush isn't working with non-repeated images
  *
  * - Correct repeat mode; right now, every surface source is EXTEND_REPEAT
  *   - implement EXTEND_NONE (?? probably need to clip to the extents of the source)
  *   - implement EXTEND_REFLECT (create temporary and copy 4x, then EXTEND_REPEAT that)
  *
- * - Implement dashing
- *
  * - stroke-image failure
  *
  * - Implement mask() with non-solid masks (probably will need to use a temporary and use IN)
  *
  * - Implement gradient sources
  *
  * - Make create_similar smarter -- create QPixmaps in more circumstances
  *   (e.g. if the pixmap can have alpha)