File indexing completed on 2024-04-21 04:54:15

0001 /*
0002     This file belong to the KMPlayer project, a movie player plugin for Konqueror
0003     SPDX-FileCopyrightText: 2007 Koos Vriezen <koos.vriezen@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "config-kmplayer.h"
0009 
0010 #include <cstdlib>
0011 #include <cmath>
0012 
0013 #include <QApplication>
0014 #include <QSlider>
0015 #include <QCursor>
0016 #include <QMap>
0017 #include <QPalette>
0018 #include <QDesktopWidget>
0019 #include <QX11Info>
0020 #include <QPainter>
0021 #include <QMainWindow>
0022 #include <QStatusBar>
0023 #include <QWidgetAction>
0024 #include <QTextBlock>
0025 #include <QTextDocument>
0026 #include <QAbstractTextDocumentLayout>
0027 #include <QImage>
0028 #include <QAbstractNativeEventFilter>
0029 
0030 #include <KActionCollection>
0031 #include <KLocalizedString>
0032 
0033 #include "kmplayercommon_log.h"
0034 #include "kmplayerview.h"
0035 #include "kmplayercontrolpanel.h"
0036 #include "playlistview.h"
0037 #include "viewarea.h"
0038 #ifdef KMPLAYER_WITH_CAIRO
0039 # include <cairo-xcb.h>
0040 #endif
0041 #include "mediaobject.h"
0042 #include "kmplayer_smil.h"
0043 #include "kmplayer_rp.h"
0044 #include "mediaobject.h"
0045 
0046 #include <xcb/xcb.h>
0047 
0048 using namespace KMPlayer;
0049 
0050 static qreal pixel_device_ratio;
0051 
0052 //-------------------------------------------------------------------------
0053 
0054 #ifdef KMPLAYER_WITH_CAIRO
0055 static void clearSurface (cairo_t *cr, const IRect &rect) {
0056     cairo_save (cr);
0057     cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
0058     cairo_rectangle (cr, rect.x (), rect.y (), rect.width (), rect.height ());
0059     cairo_fill (cr);
0060     cairo_restore (cr);
0061 }
0062 
0063 void ImageData::copyImage (Surface *s, const SSize &sz, cairo_surface_t *similar, CalculatedSizer *zoom) {
0064     cairo_surface_t *src_sf;
0065     bool clear = false;
0066     int w = sz.width;
0067     int h = sz.height;
0068 
0069     if (surface) {
0070         src_sf = surface;
0071     } else {
0072         if (image->depth () < 24) {
0073             QImage qi = image->convertToFormat (QImage::Format_RGB32);
0074             *image = qi;
0075         }
0076         src_sf = cairo_image_surface_create_for_data (
0077                 image->bits (),
0078                 has_alpha ? CAIRO_FORMAT_ARGB32:CAIRO_FORMAT_RGB24,
0079                 width, height, image->bytesPerLine ());
0080         if (flags & ImagePixmap && !(flags & ImageAnimated)) {
0081             surface = cairo_surface_create_similar (similar,
0082                     has_alpha ? CAIRO_CONTENT_COLOR_ALPHA : CAIRO_CONTENT_COLOR,
0083                     width, height);
0084             cairo_pattern_t *pat = cairo_pattern_create_for_surface (src_sf);
0085             cairo_pattern_set_extend (pat, CAIRO_EXTEND_NONE);
0086             cairo_t *cr = cairo_create (surface);
0087             cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
0088             cairo_set_source (cr, pat);
0089             cairo_paint (cr);
0090             cairo_destroy (cr);
0091             cairo_pattern_destroy (pat);
0092             cairo_surface_destroy (src_sf);
0093             src_sf = surface;
0094             delete image;
0095             image = nullptr;
0096         }
0097     }
0098 
0099     cairo_pattern_t *img_pat = cairo_pattern_create_for_surface (src_sf);
0100     cairo_pattern_set_extend (img_pat, CAIRO_EXTEND_NONE);
0101     if (zoom) {
0102         cairo_matrix_t mat;
0103         Single zx, zy, zw, zh;
0104         zoom->calcSizes (nullptr, nullptr, width, height, zx, zy, zw, zh);
0105         cairo_matrix_init_translate (&mat, zx, zy);
0106         cairo_matrix_scale (&mat, 1.0 * zw/w, 1.0 * zh/h);
0107         cairo_pattern_set_matrix (img_pat, &mat);
0108     } else if (w != width && h != height) {
0109         cairo_matrix_t mat;
0110         cairo_matrix_init_scale (&mat, 1.0 * width/w, 1.0 * height/h);
0111         cairo_pattern_set_matrix (img_pat, &mat);
0112     }
0113     if (!s->surface)
0114         s->surface = cairo_surface_create_similar (similar,
0115                 has_alpha ?
0116                 CAIRO_CONTENT_COLOR_ALPHA : CAIRO_CONTENT_COLOR, w, h);
0117     else
0118         clear = true;
0119     cairo_t *cr = cairo_create (s->surface);
0120     if (clear)
0121         clearSurface (cr, IRect (0, 0, w, h));
0122     cairo_set_source (cr, img_pat);
0123     cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
0124     cairo_paint (cr);
0125     cairo_destroy (cr);
0126 
0127     cairo_pattern_destroy (img_pat);
0128     if (!surface)
0129         cairo_surface_destroy (src_sf);
0130 }
0131 #endif
0132 
0133 //-------------------------------------------------------------------------
0134 
0135 #define REGION_SCROLLBAR_WIDTH 20
0136 
0137 #ifdef KMPLAYER_WITH_CAIRO
0138 
0139 # define CAIRO_SET_SOURCE_RGB(cr,c)           \
0140     cairo_set_source_rgb ((cr),               \
0141             1.0 * (((c) >> 16) & 0xff) / 255, \
0142             1.0 * (((c) >> 8) & 0xff) / 255,  \
0143             1.0 * (((c)) & 0xff) / 255)
0144 
0145 # define CAIRO_SET_SOURCE_ARGB(cr,c)          \
0146     cairo_set_source_rgba ((cr),              \
0147             1.0 * (((c) >> 16) & 0xff) / 255, \
0148             1.0 * (((c) >> 8) & 0xff) / 255,  \
0149             1.0 * (((c)) & 0xff) / 255,       \
0150             1.0 * (((c) >> 24) & 0xff) / 255)
0151 
0152 struct PaintContext
0153 {
0154     PaintContext (const Matrix& m, const IRect& c)
0155         : matrix (m)
0156         , clip (c)
0157         , fit (fit_default)
0158         , bg_repeat (SMIL::RegionBase::BgRepeat)
0159         , bg_image (nullptr)
0160     {}
0161     Matrix matrix;
0162     IRect clip;
0163     Fit fit;
0164     SMIL::RegionBase::BackgroundRepeat bg_repeat;
0165     ImageData *bg_image;
0166 };
0167 
0168 class CairoPaintVisitor : public Visitor, public PaintContext
0169 {
0170     cairo_surface_t * cairo_surface;
0171     // stack vars need for transitions
0172     TransitionModule *cur_transition;
0173     cairo_pattern_t * cur_pat;
0174     cairo_matrix_t cur_mat;
0175     float opacity;
0176     bool toplevel;
0177 
0178     void traverseRegion (Node *reg, Surface *s);
0179     void updateExternal (SMIL::MediaType *av, SurfacePtr s);
0180     void paint (TransitionModule *trans, MediaOpacity mopacity, Surface *s,
0181                 const IPoint &p, const IRect &);
0182     void video (Mrl *mt, Surface *s);
0183 public:
0184     cairo_t * cr;
0185     CairoPaintVisitor (cairo_surface_t * cs, Matrix m,
0186             const IRect & rect, QColor c=QColor(), bool toplevel=false);
0187     ~CairoPaintVisitor () override;
0188     using Visitor::visit;
0189     void visit (Node *) override;
0190     void visit (SMIL::Smil *) override;
0191     void visit (SMIL::Layout *) override;
0192     void visit (SMIL::RegionBase *) override;
0193     void visit (SMIL::Transition *) override;
0194     void visit (SMIL::TextMediaType *) override;
0195     void visit (SMIL::Brush *) override;
0196     void visit (SMIL::SmilText *) override;
0197     void visit (SMIL::RefMediaType *) override;
0198     void visit (RP::Imfl *) override;
0199     void visit (RP::Fill *) override;
0200     void visit (RP::Fadein *) override;
0201     void visit (RP::Fadeout *) override;
0202     void visit (RP::Crossfade *) override;
0203     void visit (RP::Wipe *) override;
0204     void visit (RP::ViewChange *) override;
0205 };
0206 
0207 CairoPaintVisitor::CairoPaintVisitor (cairo_surface_t * cs, Matrix m,
0208         const IRect & rect, QColor c, bool top)
0209  : PaintContext (m, rect), cairo_surface (cs), toplevel (top)
0210 {
0211     cr = cairo_create (cs);
0212     if (toplevel) {
0213         cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
0214         cairo_set_tolerance (cr, 0.5 );
0215         //cairo_push_group (cr);
0216         cairo_set_source_rgb (cr,
0217            1.0 * c.red () / 255, 1.0 * c.green () / 255, 1.0 * c.blue () / 255);
0218         cairo_rectangle (cr, rect.x(), rect.y(), rect.width(), rect.height());
0219         cairo_fill (cr);
0220     } else {
0221         clearSurface (cr, rect);
0222     }
0223 }
0224 
0225 CairoPaintVisitor::~CairoPaintVisitor () {
0226     /*if (toplevel) {
0227         cairo_pattern_t * pat = cairo_pop_group (cr);
0228         //cairo_pattern_set_extend (pat, CAIRO_EXTEND_NONE);
0229         cairo_set_source (cr, pat);
0230         cairo_rectangle (cr, clip.x, clip.y, clip.w, clip.h);
0231         cairo_fill (cr);
0232         cairo_pattern_destroy (pat);
0233     }*/
0234     cairo_destroy (cr);
0235 }
0236 
0237 void CairoPaintVisitor::visit (Node * n) {
0238     qCWarning(LOG_KMPLAYER_COMMON) << "Paint called on " << n->nodeName();
0239 }
0240 
0241 void CairoPaintVisitor::visit (SMIL::Smil *s) {
0242     if (s->active () && s->layout_node)
0243         s->layout_node->accept (this);
0244 }
0245 
0246 void CairoPaintVisitor::traverseRegion (Node *node, Surface *s) {
0247     ConnectionList *nl = nodeMessageReceivers (node, MsgSurfaceAttach);
0248     if (nl) {
0249         for (Connection *c = nl->first(); c; c = nl->next ())
0250             if (c->connecter)
0251                 c->connecter->accept (this);
0252     }
0253     /*for (SurfacePtr c = s->lastChild (); c; c = c->previousSibling ()) {
0254         if (c->node && c->node->id != SMIL::id_node_region &&
0255         c->node && c->node->id != SMIL::id_node_root_layout)
0256             c->node->accept (this);
0257         else
0258             break;
0259     }*/
0260     // finally visit region children
0261     for (SurfacePtr c = s->firstChild (); c; c = c->nextSibling ()) {
0262         if (c->node && c->node->id == SMIL::id_node_region)
0263             c->node->accept (this);
0264         else
0265             break;
0266     }
0267     s->dirty = false;
0268 }
0269 
0270 void CairoPaintVisitor::visit (SMIL::Layout *layout) {
0271     if (layout->root_layout)
0272         layout->root_layout->accept (this);
0273 }
0274 
0275 
0276 static void cairoDrawRect (cairo_t *cr, unsigned int color,
0277         int x, int y, int w, int h) {
0278     CAIRO_SET_SOURCE_ARGB (cr, color);
0279     cairo_rectangle (cr, x, y, w, h);
0280     cairo_fill (cr);
0281 }
0282 
0283 void CairoPaintVisitor::visit (SMIL::RegionBase *reg) {
0284     Surface *s = (Surface *) reg->role (RoleDisplay);
0285     if (s) {
0286         SRect rect = s->bounds;
0287 
0288         IRect scr = matrix.toScreen (rect);
0289         if (clip.intersect (scr).isEmpty ())
0290             return;
0291         PaintContext ctx_save = *(PaintContext *) this;
0292         matrix = Matrix (rect.x(), rect.y(), s->xscale, s->yscale);
0293         matrix.transform (ctx_save.matrix);
0294         clip = clip.intersect (scr);
0295         if (SMIL::RegionBase::BgInherit != reg->bg_repeat)
0296             bg_repeat = reg->bg_repeat;
0297         cairo_save (cr);
0298 
0299         Surface *cs = s->firstChild ();
0300         if (!s->virtual_size.isEmpty ())
0301             matrix.translate (-s->x_scroll, -s->y_scroll);
0302 
0303         if (fit_default != reg->fit)
0304             fit = reg->fit;
0305 
0306         ImageMedia *im = reg->media_info
0307             ? (ImageMedia *) reg->media_info->media
0308             : nullptr;
0309 
0310         ImageData *bg_img = im && !im->isEmpty() ? im->cached_img.ptr () : nullptr;
0311         if (reg->background_image == "inherit")
0312             bg_img = bg_image;
0313         else
0314             bg_image = bg_img;
0315         unsigned int bg_alpha = s->background_color & 0xff000000;
0316         if ((SMIL::RegionBase::ShowAlways == reg->show_background ||
0317                     reg->m_AttachedMediaTypes.first ()) &&
0318                 (bg_alpha || bg_img)) {
0319             cairo_save (cr);
0320             if (bg_alpha) {
0321                 cairo_rectangle (cr,
0322                         clip.x (), clip.y (), clip.width (), clip.height ());
0323                 if (bg_alpha < 0xff000000) {
0324                     CAIRO_SET_SOURCE_ARGB (cr, s->background_color);
0325                     cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
0326                     cairo_fill (cr);
0327                     cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
0328                 } else {
0329                     CAIRO_SET_SOURCE_RGB (cr, s->background_color);
0330                     cairo_fill (cr);
0331                 }
0332             }
0333             if (bg_img) {
0334                 Single w = bg_img->width;
0335                 Single h = bg_img->height;
0336                 matrix.getWH (w, h);
0337                 if (!s->surface)
0338                     bg_img->copyImage (s, SSize (w, h), cairo_surface);
0339                 if (bg_img->has_alpha)
0340                     cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
0341                 cairo_pattern_t *pat = cairo_pattern_create_for_surface (s->surface);
0342                 cairo_pattern_set_extend (pat, CAIRO_EXTEND_REPEAT);
0343                 cairo_matrix_t mat;
0344                 cairo_matrix_init_translate (&mat, -scr.x (), -scr.y ());
0345                 cairo_pattern_set_matrix (pat, &mat);
0346                 cairo_set_source (cr, pat);
0347                 int cw = clip.width ();
0348                 int ch = clip.height ();
0349                 switch (bg_repeat) {
0350                 case SMIL::RegionBase::BgRepeatX:
0351                     if (h < ch)
0352                         ch = h;
0353                     break;
0354                 case SMIL::RegionBase::BgRepeatY:
0355                     if (w < cw)
0356                         cw = w;
0357                     break;
0358                 case SMIL::RegionBase::BgNoRepeat:
0359                     if (w < cw)
0360                         cw = w;
0361                     if (h < ch)
0362                         ch = h;
0363                     break;
0364                 default:
0365                     break;
0366                 }
0367                 cairo_rectangle (cr, clip.x (), clip.y (), cw, ch);
0368                 cairo_fill (cr);
0369                 cairo_pattern_destroy (pat);
0370                 if (bg_img->has_alpha)
0371                     cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
0372             }
0373             cairo_restore (cr);
0374         }
0375         traverseRegion (reg, s);
0376         cs = s->firstChild ();
0377         if (cs && (s->scroll || cs->scroll) && cs == s->lastChild ()) {
0378             SRect r = cs->bounds;
0379             if (r.width () > rect.width () || r.height () > rect.height ()) {
0380                 if (s->virtual_size.isEmpty ())
0381                     s->x_scroll = s->y_scroll = 0;
0382                 s->virtual_size = r.size;
0383                 matrix.getWH (s->virtual_size.width, s->virtual_size.height);
0384                 s->virtual_size.width += REGION_SCROLLBAR_WIDTH;
0385                 s->virtual_size.height += REGION_SCROLLBAR_WIDTH;
0386                 const int vy = s->virtual_size.height;
0387                 const int vw = s->virtual_size.width;
0388                 int sbw = REGION_SCROLLBAR_WIDTH;
0389                 int sbx = scr.x () + scr.width () - sbw;
0390                 int sby = scr.y ();
0391                 int sbh = scr.height () - REGION_SCROLLBAR_WIDTH;
0392                 IRect sb_clip = clip.intersect (IRect (sbx, sby, sbw, sbh));
0393                 if (!sb_clip.isEmpty ()) {
0394                     int knob_h = sbh * scr.height () / vy;
0395                     int knob_y = scr.y () + s->y_scroll * sbh / vy;
0396                     IRect knob (sbx, knob_y, sbw, knob_h);
0397                     cairo_save (cr);
0398                     cairo_rectangle (cr, sb_clip.x (), sb_clip.y (),
0399                             sb_clip.width (), sb_clip.height ());
0400                     cairo_clip (cr);
0401                     cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
0402                     cairo_set_line_width (cr, 2);
0403                     CAIRO_SET_SOURCE_ARGB (cr, 0x80A0A0A0);
0404                     cairo_rectangle (cr, sbx + 1, sby + 1, sbw - 2, sbh - 2);
0405                     cairo_stroke (cr);
0406                     if (s->y_scroll)
0407                         cairoDrawRect (cr, 0x80000000,
0408                                 sbx + 2, sby + 2,
0409                                 sbw - 4, knob.y() - sby - 2);
0410                     cairoDrawRect (cr, 0x80808080,
0411                             knob.x() + 2, knob.y(),
0412                             knob.width() - 4, knob.height());
0413                     if (sby + sbh - knob.y() - knob.height() - 2 > 0)
0414                         cairoDrawRect (cr, 0x80000000,
0415                                 sbx + 2, knob.y() + knob.height(),
0416                                 sbw - 4, sby + sbh - knob.y() -knob.height()-2);
0417                     cairo_restore (cr);
0418                 }
0419                 sbh = REGION_SCROLLBAR_WIDTH;
0420                 sbx = scr.x ();
0421                 sby = scr.y () + scr.height () - sbh;
0422                 sbw = scr.width () - REGION_SCROLLBAR_WIDTH;
0423                 sb_clip = clip.intersect (IRect (sbx, sby, sbw, sbh));
0424                 if (!sb_clip.isEmpty ()) {
0425                     int knob_w = sbw * scr.width () / vw;
0426                     int knob_x = scr.x () + s->x_scroll * sbw / vw;
0427                     IRect knob (knob_x, sby, knob_w, sbh);
0428                     cairo_save (cr);
0429                     cairo_rectangle (cr, sb_clip.x (), sb_clip.y (),
0430                             sb_clip.width (), sb_clip.height ());
0431                     cairo_clip (cr);
0432                     cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
0433                     cairo_set_line_width (cr, 2);
0434                     CAIRO_SET_SOURCE_ARGB (cr, 0x80A0A0A0);
0435                     cairo_rectangle (cr, sbx + 1, sby + 1, sbw - 2, sbh - 2);
0436                     cairo_stroke (cr);
0437                     if (s->x_scroll)
0438                         cairoDrawRect (cr, 0x80000000,
0439                                 sbx + 2, sby + 2,
0440                                 knob.x() - sbx - 2, sbh - 4);
0441                     cairoDrawRect (cr, 0x80808080,
0442                             knob.x(), knob.y() + 2,
0443                             knob.width(), knob.height() - 4);
0444                     if (sbx + sbw - knob.x() - knob.width() - 2 > 0)
0445                         cairoDrawRect (cr, 0x80000000,
0446                                 knob.x() + knob.width(), sby + 2,
0447                                 sbx + sbw - knob.x() - knob.width()-2, sbh - 4);
0448                     cairo_restore (cr);
0449                 }
0450             }
0451         }
0452         cairo_restore (cr);
0453         *(PaintContext *) this = ctx_save;
0454         s->dirty = false;
0455     }
0456 }
0457 
0458 #define CAIRO_SET_PATTERN_COND(cr,pat,mat)                      \
0459     if (pat) {                                                  \
0460         cairo_pattern_set_extend (cur_pat, CAIRO_EXTEND_NONE);  \
0461         cairo_pattern_set_matrix (pat, &mat);                   \
0462         cairo_pattern_set_filter (pat, CAIRO_FILTER_FAST);      \
0463         cairo_set_source (cr, pat);                             \
0464     }
0465 
0466 void CairoPaintVisitor::visit (SMIL::Transition *trans) {
0467     float perc = trans->start_progress + (trans->end_progress - trans->start_progress)*cur_transition->trans_gain;
0468     if (cur_transition->trans_out_active)
0469         perc = 1.0 - perc;
0470     if (SMIL::Transition::Fade == trans->type) {
0471         CAIRO_SET_PATTERN_COND(cr, cur_pat, cur_mat)
0472         cairo_rectangle (cr, clip.x(), clip.y(), clip.width(), clip.height());
0473         opacity = perc;
0474     } else if (SMIL::Transition::BarWipe == trans->type) {
0475         IRect rect;
0476         if (SMIL::Transition::SubTopToBottom == trans->sub_type) {
0477             if (SMIL::Transition::dir_reverse == trans->direction) {
0478                 int dy = (int) ((1.0 - perc) * clip.height ());
0479                 rect = IRect (clip.x (), clip.y () + dy,
0480                         clip.width (), clip.height () - dy);
0481             } else {
0482                 rect = IRect (clip.x (), clip.y (),
0483                         clip.width (), (int) (perc * clip.height ()));
0484             }
0485         } else {
0486             if (SMIL::Transition::dir_reverse == trans->direction) {
0487                 int dx = (int) ((1.0 - perc) * clip.width ());
0488                 rect = IRect (clip.x () + dx, clip.y (),
0489                         clip.width () - dx, clip.height ());
0490             } else {
0491                 rect = IRect (clip.x (), clip.y (),
0492                         (int) (perc * clip.width ()), clip.height ());
0493             }
0494         }
0495         cairo_rectangle (cr, rect.x(), rect.y(), rect.width(), rect.height());
0496         CAIRO_SET_PATTERN_COND(cr, cur_pat, cur_mat)
0497     } else if (SMIL::Transition::PushWipe == trans->type) {
0498         int dx = 0, dy = 0;
0499         if (SMIL::Transition::SubFromTop == trans->sub_type)
0500             dy = -(int) ((1.0 - perc) * clip.height ());
0501         else if (SMIL::Transition::SubFromRight == trans->sub_type)
0502             dx = (int) ((1.0 - perc) * clip.width ());
0503         else if (SMIL::Transition::SubFromBottom == trans->sub_type)
0504             dy = (int) ((1.0 - perc) * clip.height ());
0505         else //if (SMIL::Transition::SubFromLeft == trans->sub_type)
0506             dx = -(int) ((1.0 - perc) * clip.width ());
0507         cairo_matrix_translate (&cur_mat, -dx, -dy);
0508         IRect rect = clip.intersect (IRect (clip.x () + dx, clip.y () + dy,
0509                     clip.width (), clip.height ()));
0510         cairo_rectangle (cr, rect.x(), rect.y(), rect.width(), rect.height());
0511         CAIRO_SET_PATTERN_COND(cr, cur_pat, cur_mat)
0512     } else if (SMIL::Transition::IrisWipe == trans->type) {
0513         CAIRO_SET_PATTERN_COND(cr, cur_pat, cur_mat)
0514         if (SMIL::Transition::SubDiamond == trans->sub_type) {
0515             cairo_rectangle (cr, clip.x(), clip.y(),clip.width(),clip.height());
0516             cairo_clip (cr);
0517             int dx = (int) (perc * clip.width ());
0518             int dy = (int) (perc * clip.height ());
0519             int mx = clip.x () + clip.width ()/2;
0520             int my = clip.y () + clip.height ()/2;
0521             cairo_new_path (cr);
0522             cairo_move_to (cr, mx, my - dy);
0523             cairo_line_to (cr, mx + dx, my);
0524             cairo_line_to (cr, mx, my + dy);
0525             cairo_line_to (cr, mx - dx, my);
0526             cairo_close_path (cr);
0527         } else { // SubRectangle
0528             int dx = (int) (0.5 * (1 - perc) * clip.width ());
0529             int dy = (int) (0.5 * (1 - perc) * clip.height ());
0530             cairo_rectangle (cr, clip.x () + dx, clip.y () + dy,
0531                     clip.width () - 2 * dx, clip.height () -2 * dy);
0532         }
0533     } else if (SMIL::Transition::ClockWipe == trans->type) {
0534         cairo_rectangle (cr, clip.x(), clip.y(), clip.width(), clip.height());
0535         cairo_clip (cr);
0536         int mx = clip.x () + clip.width ()/2;
0537         int my = clip.y () + clip.height ()/2;
0538         cairo_new_path (cr);
0539         cairo_move_to (cr, mx, my);
0540         float hw = 1.0 * clip.width ()/2;
0541         float hh = 1.0 * clip.height ()/2;
0542         float radius = sqrtf (hw * hw + hh * hh);
0543         float phi;
0544         switch (trans->sub_type) {
0545             case SMIL::Transition::SubClockwiseThree:
0546                 phi = 0;
0547                 break;
0548             case SMIL::Transition::SubClockwiseSix:
0549                 phi = M_PI / 2;
0550                 break;
0551             case SMIL::Transition::SubClockwiseNine:
0552                 phi = M_PI;
0553                 break;
0554             default: // Twelve
0555                 phi = -M_PI / 2;
0556                 break;
0557         }
0558         if (SMIL::Transition::dir_reverse == trans->direction)
0559             cairo_arc_negative (cr, mx, my, radius, phi, phi - 2 * M_PI * perc);
0560         else
0561             cairo_arc (cr, mx, my, radius, phi, phi + 2 * M_PI * perc);
0562         cairo_close_path (cr);
0563         CAIRO_SET_PATTERN_COND(cr, cur_pat, cur_mat)
0564     } else if (SMIL::Transition::BowTieWipe == trans->type) {
0565         cairo_rectangle (cr, clip.x(), clip.y(), clip.width(), clip.height());
0566         cairo_clip (cr);
0567         int mx = clip.x () + clip.width ()/2;
0568         int my = clip.y () + clip.height ()/2;
0569         cairo_new_path (cr);
0570         cairo_move_to (cr, mx, my);
0571         float hw = 1.0 * clip.width ()/2;
0572         float hh = 1.0 * clip.height ()/2;
0573         float radius = sqrtf (hw * hw + hh * hh);
0574         float phi;
0575         switch (trans->sub_type) {
0576             case SMIL::Transition::SubHorizontal:
0577                 phi = 0;
0578                 break;
0579             default: // Vertical
0580                 phi = -M_PI / 2;
0581                 break;
0582         }
0583         float dphi = 0.5 * M_PI * perc;
0584         cairo_arc (cr, mx, my, radius, phi - dphi, phi + dphi);
0585         cairo_close_path (cr);
0586         cairo_new_sub_path (cr);
0587         cairo_move_to (cr, mx, my);
0588         if (SMIL::Transition::SubHorizontal == trans->sub_type)
0589             cairo_arc (cr, mx, my, radius, M_PI + phi - dphi, M_PI + phi +dphi);
0590         else
0591             cairo_arc (cr, mx, my, radius, -phi - dphi, -phi + dphi);
0592         cairo_close_path (cr);
0593         CAIRO_SET_PATTERN_COND(cr, cur_pat, cur_mat)
0594     } else if (SMIL::Transition::EllipseWipe == trans->type) {
0595         cairo_rectangle (cr, clip.x(), clip.y(), clip.width(), clip.height());
0596         cairo_clip (cr);
0597         int mx = clip.x () + clip.width ()/2;
0598         int my = clip.y () + clip.height ()/2;
0599         float hw = (double) clip.width ()/2;
0600         float hh = (double) clip.height ()/2;
0601         float radius = sqrtf (hw * hw + hh * hh);
0602         cairo_save (cr);
0603         cairo_new_path (cr);
0604         cairo_translate (cr, (int) mx, (int) my);
0605         cairo_move_to (cr, - Single (radius), 0);
0606         if (SMIL::Transition::SubHorizontal == trans->sub_type)
0607             cairo_scale (cr, 1.0, 0.6);
0608         else if (SMIL::Transition::SubVertical == trans->sub_type)
0609             cairo_scale (cr, 0.6, 1.0);
0610         cairo_arc (cr, 0, 0, perc * radius, 0, 2 * M_PI);
0611         cairo_close_path (cr);
0612         cairo_restore (cr);
0613         CAIRO_SET_PATTERN_COND(cr, cur_pat, cur_mat)
0614     }
0615 }
0616 
0617 void CairoPaintVisitor::video (Mrl *m, Surface *s) {
0618     if (m->media_info &&
0619             m->media_info->media &&
0620             (MediaManager::Audio == m->media_info->type ||
0621              MediaManager::AudioVideo == m->media_info->type)) {
0622         AudioVideoMedia *avm = static_cast<AudioVideoMedia *> (m->media_info->media);
0623         if (avm->viewer ()) {
0624             if (s &&
0625                     avm->process &&
0626                     avm->process->state () > IProcess::Ready &&
0627                     strcmp (m->nodeName (), "audio")) {
0628                 s->xscale = s->yscale = 1; // either scale width/height or use bounds
0629                 avm->viewer ()->setGeometry (s->toScreen (s->bounds.size));
0630             } else {
0631                 avm->viewer ()->setGeometry (IRect (-60, -60, 50, 50));
0632             }
0633         }
0634     }
0635 }
0636 
0637 void CairoPaintVisitor::visit (SMIL::RefMediaType *ref) {
0638     Surface *s = ref->surface ();
0639     if (s && ref->external_tree) {
0640         updateExternal (ref, s);
0641         return;
0642     }
0643     if (!ref->media_info)
0644         return;
0645     if (fit_default != fit
0646             && fit_default == ref->fit
0647             && fit != ref->effective_fit) {
0648         ref->effective_fit = fit;
0649         s->resize (ref->calculateBounds(), false);
0650     }
0651     if (ref->media_info->media &&
0652             ref->media_info->media->type () == MediaManager::Image) {
0653         if (!s)
0654             return;
0655 
0656         IRect scr = matrix.toScreen (s->bounds);
0657         IRect clip_rect = clip.intersect (scr);
0658         if (clip_rect.isEmpty ())
0659             return;
0660 
0661         ImageMedia *im = static_cast <ImageMedia *> (ref->media_info->media);
0662         ImageData *id = im ? im->cached_img.ptr () : nullptr;
0663         if (id && id->flags == ImageData::ImageScalable)
0664             im->render (scr.size);
0665         if (!id || im->isEmpty () || ref->size.isEmpty ()) {
0666             s->remove();
0667             return;
0668         }
0669         if (!s->surface || s->dirty)
0670             id->copyImage (s, SSize (scr.width (), scr.height ()), cairo_surface, ref->pan_zoom);
0671         paint (&ref->transition, ref->media_opacity, s, scr.point, clip_rect);
0672         s->dirty = false;
0673     } else {
0674         video (ref, s);
0675     }
0676 }
0677 
0678 void CairoPaintVisitor::paint (TransitionModule *trans,
0679         MediaOpacity mopacity, Surface *s,
0680         const IPoint &point, const IRect &rect) {
0681     cairo_save (cr);
0682     opacity = 1.0;
0683     cairo_matrix_init_translate (&cur_mat, -point.x, -point.y);
0684     cur_pat = cairo_pattern_create_for_surface (s->surface);
0685     if (trans->active_trans) {
0686         IRect clip_save = clip;
0687         clip = rect;
0688         cur_transition = trans;
0689         trans->active_trans->accept (this);
0690         clip = clip_save;
0691     } else {
0692         cairo_pattern_set_extend (cur_pat, CAIRO_EXTEND_NONE);
0693         cairo_pattern_set_matrix (cur_pat, &cur_mat);
0694         cairo_pattern_set_filter (cur_pat, CAIRO_FILTER_FAST);
0695         cairo_set_source (cr, cur_pat);
0696         cairo_rectangle (cr, rect.x(), rect.y(), rect.width(), rect.height());
0697     }
0698     opacity *= mopacity.opacity / 100.0;
0699     bool over = opacity < 0.99 ||
0700                 CAIRO_CONTENT_COLOR != cairo_surface_get_content (s->surface);
0701     cairo_operator_t op;
0702     if (over) {
0703         op = cairo_get_operator (cr);
0704         cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
0705     }
0706     if (opacity < 0.99) {
0707         cairo_clip (cr);
0708         cairo_paint_with_alpha (cr, opacity);
0709     } else {
0710         cairo_fill (cr);
0711     }
0712     if (over)
0713         cairo_set_operator (cr, op);
0714     cairo_pattern_destroy (cur_pat);
0715     cairo_restore (cr);
0716 }
0717 
0718 static Mrl *findActiveMrl (Node *n, bool *rp_or_smil) {
0719     Mrl *mrl = n->mrl ();
0720     if (mrl) {
0721         *rp_or_smil = (mrl->id >= SMIL::id_node_first &&
0722                 mrl->id < SMIL::id_node_last) ||
0723             (mrl->id >= RP::id_node_first &&
0724              mrl->id < RP::id_node_last);
0725         if (*rp_or_smil ||
0726                 (mrl->media_info &&
0727                  (MediaManager::Audio == mrl->media_info->type ||
0728                   MediaManager::AudioVideo == mrl->media_info->type)))
0729             return mrl;
0730     }
0731     for (Node *c = n->firstChild (); c; c = c->nextSibling ())
0732         if (c->active ()) {
0733             Mrl *m = findActiveMrl (c, rp_or_smil);
0734             if (m)
0735                 return m;
0736         }
0737     return nullptr;
0738 }
0739 
0740 void CairoPaintVisitor::updateExternal (SMIL::MediaType *av, SurfacePtr s) {
0741     bool rp_or_smil = false;
0742     Mrl *ext_mrl = findActiveMrl (av->external_tree.ptr (), &rp_or_smil);
0743     if (!ext_mrl)
0744         return;
0745     if (!rp_or_smil) {
0746         video (ext_mrl, s.ptr ());
0747         return;
0748     }
0749     IRect scr = matrix.toScreen (s->bounds);
0750     IRect clip_rect = clip.intersect (scr);
0751     if (clip_rect.isEmpty ())
0752         return;
0753     if (!s->surface || s->dirty) {
0754         Matrix m = matrix;
0755         m.translate (-scr.x (), -scr.y ());
0756         m.scale (s->xscale, s->yscale);
0757         IRect r (clip_rect.x() - scr.x () - 1, clip_rect.y() - scr.y () - 1,
0758                 clip_rect.width() + 3, clip_rect.height() + 3);
0759         if (!s->surface) {
0760             s->surface = cairo_surface_create_similar (cairo_surface,
0761                     CAIRO_CONTENT_COLOR_ALPHA, scr.width (), scr.height ());
0762             r = IRect (0, 0, scr.size);
0763         }
0764         CairoPaintVisitor visitor (s->surface, m, r);
0765         ext_mrl->accept (&visitor);
0766         s->dirty = false;
0767     }
0768     paint (&av->transition, av->media_opacity, s.ptr (), scr.point, clip_rect);
0769 }
0770 
0771 static void setAlignment (QTextDocument &td, unsigned char align) {
0772     QTextOption opt = td.defaultTextOption();
0773     if (SmilTextProperties::AlignLeft == align)
0774         opt.setAlignment (Qt::AlignLeft);
0775     else if (SmilTextProperties::AlignCenter == align)
0776         opt.setAlignment (Qt::AlignCenter);
0777     else if (SmilTextProperties::AlignRight == align)
0778         opt.setAlignment (Qt::AlignRight);
0779     td.setDefaultTextOption (opt);
0780 }
0781 
0782 static void calculateTextDimensions (const QFont& font,
0783         const QString& text, Single w, Single h, Single maxh,
0784         int *pxw, int *pxh, bool markup_text,
0785         unsigned char align = SmilTextProperties::AlignLeft) {
0786     QTextDocument td;
0787     td.setDefaultFont( font );
0788     td.setDocumentMargin (0);
0789     QImage img (QSize ((int)w, (int)h), QImage::Format_RGB32);
0790     td.setPageSize (QSize ((int)w, (int)maxh));
0791     td.documentLayout()->setPaintDevice (&img);
0792     if (markup_text)
0793         td.setHtml( text );
0794     else
0795         td.setPlainText( text );
0796     setAlignment (td, align);
0797     QRectF r = td.documentLayout()->blockBoundingRect (td.lastBlock());
0798     *pxw = (int)td.idealWidth ();
0799     *pxh = (int)(r.y() + r.height());
0800     *pxw = qMin( (int)(*pxw + pixel_device_ratio), (int)w);
0801 }
0802 
0803 static cairo_t *createContext (cairo_surface_t *similar, Surface *s, int w, int h) {
0804     unsigned int bg_alpha = s->background_color & 0xff000000;
0805     bool clear = s->surface;
0806     if (!s->surface)
0807         s->surface = cairo_surface_create_similar (similar,
0808                 bg_alpha < 0xff000000
0809                 ? CAIRO_CONTENT_COLOR_ALPHA
0810                 : CAIRO_CONTENT_COLOR,
0811                 w, h);
0812     cairo_t *cr = cairo_create (s->surface);
0813     if (clear)
0814         clearSurface (cr, IRect (0, 0, w, h));
0815     cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
0816 
0817     if (bg_alpha) {
0818         if (bg_alpha < 0xff000000)
0819             CAIRO_SET_SOURCE_ARGB (cr, s->background_color);
0820         else
0821             CAIRO_SET_SOURCE_RGB (cr, s->background_color);
0822         cairo_paint (cr);
0823     }
0824     return cr;
0825 }
0826 
0827 void CairoPaintVisitor::visit (SMIL::TextMediaType * txt) {
0828     if (!txt->media_info || !txt->media_info->media)
0829         return;
0830     TextMedia *tm = static_cast <TextMedia *> (txt->media_info->media);
0831     Surface *s = txt->surface ();
0832     if (!s)
0833         return;
0834     if (!s->surface) {
0835         txt->size = SSize ();
0836         s->bounds = txt->calculateBounds ();
0837     }
0838     IRect scr = matrix.toScreen (s->bounds);
0839     if (!s->surface || s->dirty) {
0840 
0841         int w = scr.width ();
0842         int pxw, pxh;
0843         Single ft_size = w * txt->font_size / (double)s->bounds.width ();
0844         bool clear = s->surface;
0845 
0846         QFont font (txt->font_name);
0847         font.setPixelSize(ft_size);
0848         if (clear) {
0849             pxw = scr.width ();
0850             pxh = scr.height ();
0851         } else {
0852             calculateTextDimensions (font, tm->text,
0853                     w, 2 * ft_size, scr.height (), &pxw, &pxh, false);
0854         }
0855         QTextDocument td;
0856         td.setDocumentMargin (0);
0857         td.setDefaultFont (font);
0858         bool have_alpha = (s->background_color & 0xff000000) < 0xff000000;
0859         QImage img (QSize (pxw, pxh), have_alpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
0860         img.fill (s->background_color);
0861         td.setPageSize (QSize (pxw, pxh + (int)ft_size));
0862         td.documentLayout()->setPaintDevice (&img);
0863         setAlignment (td, 1 + (int)txt->halign);
0864         td.setPlainText (tm->text);
0865         QPainter painter;
0866         painter.begin (&img);
0867         QAbstractTextDocumentLayout::PaintContext ctx;
0868         ctx.clip = QRect (0, 0, pxw, pxh);
0869         ctx.palette.setColor (QPalette::Text, QColor (QRgb (txt->font_color)));
0870         td.documentLayout()->draw (&painter, ctx);
0871         painter.end();
0872 
0873         cairo_t *cr_txt = createContext (cairo_surface, s, pxw, pxh);
0874         cairo_surface_t *src_sf = cairo_image_surface_create_for_data (
0875                 img.bits (),
0876                 have_alpha ? CAIRO_FORMAT_ARGB32:CAIRO_FORMAT_RGB24,
0877                 img.width(), img.height(), img.bytesPerLine ());
0878         cairo_pattern_t *pat = cairo_pattern_create_for_surface (src_sf);
0879         cairo_pattern_set_extend (pat, CAIRO_EXTEND_NONE);
0880         cairo_set_operator (cr_txt, CAIRO_OPERATOR_SOURCE);
0881         cairo_set_source (cr_txt, pat);
0882         cairo_paint (cr_txt);
0883         cairo_pattern_destroy (pat);
0884         cairo_surface_destroy (src_sf);
0885         cairo_destroy (cr_txt);
0886 
0887         // update bounds rect
0888         SRect rect = matrix.toUser (IRect (scr.point, ISize (pxw, pxh)));
0889         txt->size = rect.size;
0890         s->bounds = txt->calculateBounds ();
0891 
0892         // update coord. for painting below
0893         scr = matrix.toScreen (s->bounds);
0894     }
0895     IRect clip_rect = clip.intersect (scr);
0896     if (!clip_rect.isEmpty ())
0897         paint (&txt->transition, txt->media_opacity, s, scr.point, clip_rect);
0898     s->dirty = false;
0899 }
0900 
0901 void CairoPaintVisitor::visit (SMIL::Brush * brush) {
0902     Surface *s = brush->surface ();
0903     if (s) {
0904         opacity = 1.0;
0905         IRect clip_rect = clip.intersect (matrix.toScreen (s->bounds));
0906         if (clip_rect.isEmpty ())
0907             return;
0908         cairo_save (cr);
0909         if (brush->transition.active_trans) {
0910             cur_transition = &brush->transition;
0911             cur_pat = nullptr;
0912             brush->transition.active_trans->accept (this);
0913         } else {
0914             cairo_rectangle (cr, clip_rect.x (), clip_rect.y (),
0915                     clip_rect.width (), clip_rect.height ());
0916         }
0917         unsigned int color = brush->color.color;
0918         if (!color) {
0919             color = brush->background_color.color;
0920             opacity *= brush->background_color.opacity / 100.0;
0921         } else {
0922             opacity *= brush->color.opacity / 100.0;
0923         }
0924         opacity *= brush->media_opacity.opacity / 100.0;
0925         if (opacity < 0.99) {
0926             cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
0927             cairo_set_source_rgba (cr,
0928                     1.0 * ((color >> 16) & 0xff) / 255,
0929                     1.0 * ((color >> 8) & 0xff) / 255,
0930                     1.0 * (color & 0xff) / 255,
0931                     opacity);
0932         } else {
0933             CAIRO_SET_SOURCE_RGB (cr, color);
0934         }
0935         cairo_fill (cr);
0936         if (opacity < 0.99)
0937             cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
0938         s->dirty = false;
0939         cairo_restore (cr);
0940     }
0941 }
0942 
0943 struct SmilTextBlock {
0944     SmilTextBlock (const QFont& f, const QString &t,
0945             IRect r, unsigned char a)
0946         : font (f), rich_text (t), rect (r), align (a), next (nullptr) {}
0947 
0948     QFont font;
0949     QString rich_text;
0950     IRect rect;
0951     unsigned char align;
0952 
0953     SmilTextBlock *next;
0954 };
0955 
0956 struct SmilTextInfo
0957 {
0958     SmilTextInfo (const SmilTextProperties &p) : props (p) {}
0959 
0960     void span (float scale);
0961 
0962     SmilTextProperties props;
0963     QString span_text;
0964 };
0965 
0966 class SmilTextVisitor : public Visitor
0967 {
0968 public:
0969     SmilTextVisitor (int w, float s, const SmilTextProperties &p)
0970         : first (nullptr), last (nullptr), width (w), voffset (0),
0971           scale (s), max_font_size (0), info (p) {
0972          info.span (scale);
0973     }
0974     using Visitor::visit;
0975     void visit (TextNode *) override;
0976     void visit (SMIL::TextFlow *) override;
0977     void visit (SMIL::TemporalMoment *) override;
0978 
0979     void addRichText (const QString &txt);
0980     void push ();
0981 
0982     SmilTextBlock *first;
0983     SmilTextBlock *last;
0984 
0985     int width;
0986     int voffset;
0987     float scale;
0988     float max_font_size;
0989     SmilTextInfo info;
0990     QString rich_text;
0991 };
0992 
0993 void SmilTextInfo::span (float scale) {
0994     QString s = "<span style=\"";
0995     if (props.font_size.size () > -1)
0996         s += "font-size:" + QString::number ((int)(scale * props.font_size.size ())) + "px;";
0997     s += "font-family:" + props.font_family + ";";
0998     if (props.font_color > -1)
0999         s += QString::asprintf ("color:#%06x;", props.font_color);
1000     if (props.background_color > -1)
1001         s += QString::asprintf ("background-color:#%06x;", props.background_color);
1002     if (SmilTextProperties::StyleInherit != props.font_style) {
1003         s += "font-style:";
1004         switch (props.font_style) {
1005             case SmilTextProperties::StyleOblique:
1006                 s += "oblique;";
1007                 break;
1008             case SmilTextProperties::StyleItalic:
1009                 s += "italic;";
1010                 break;
1011             default:
1012                 s += "normal;";
1013                 break;
1014         }
1015     }
1016     if (SmilTextProperties::WeightInherit != props.font_weight) {
1017         s += "font-weight:";
1018         switch (props.font_weight) {
1019             case SmilTextProperties::WeightBold:
1020                 s += "bold;";
1021                 break;
1022             default:
1023                 s += "normal;";
1024                 break;
1025         }
1026     }
1027     s += "\">";
1028     span_text = s;
1029 }
1030 
1031 void SmilTextVisitor::addRichText (const QString &txt) {
1032     if (!info.span_text.isEmpty ())
1033         rich_text += info.span_text;
1034     rich_text += txt;
1035     if (!info.span_text.isEmpty ())
1036         rich_text += "</span>";
1037 }
1038 
1039 void SmilTextVisitor::push () {
1040     if (!rich_text.isEmpty ()) {
1041         int pxw, pxh;
1042         float fs = info.props.font_size.size ();
1043         if (fs < 0)
1044             fs = TextMedia::defaultFontSize ();
1045         float maxfs = max_font_size;
1046         if (maxfs < 1.0)
1047             maxfs = fs;
1048         fs *= scale;
1049         maxfs *= scale;
1050 
1051         QFont font ("Sans");
1052         font.setPixelSize((int)fs);
1053         calculateTextDimensions (font, rich_text.toUtf8 ().constData (),
1054                 width, 2 * maxfs, 1024, &pxw, &pxh, true, info.props.text_align);
1055         int x = 0;
1056         if (SmilTextProperties::AlignCenter == info.props.text_align)
1057             x = (width - pxw) / 2;
1058         else if (SmilTextProperties::AlignRight == info.props.text_align)
1059             x = width - pxw;
1060         SmilTextBlock *block = new SmilTextBlock (font, rich_text,
1061                 IRect (x, voffset, pxw, pxh), info.props.text_align);
1062         voffset += pxh;
1063         max_font_size = 0;
1064         rich_text.clear();
1065         if (!first) {
1066             first = last = block;
1067         } else {
1068             last->next = block;
1069             last = block;
1070         }
1071     }
1072 }
1073 
1074 void SmilTextVisitor::visit (TextNode *text) {
1075     QString buffer;
1076     QTextStream out (&buffer, QIODevice::WriteOnly);
1077     out << XMLStringlet (text->nodeValue ());
1078     addRichText (buffer);
1079     if (text->nextSibling ())
1080         text->nextSibling ()->accept (this);
1081 }
1082 
1083 void SmilTextVisitor::visit (SMIL::TextFlow *flow) {
1084     bool new_block = SMIL::id_node_p == flow->id ||
1085         SMIL::id_node_br == flow->id ||
1086         SMIL::id_node_div == flow->id;
1087     if ((new_block && !rich_text.isEmpty ()) || flow->firstChild ()) {
1088         float fs = info.props.font_size.size ();
1089         if (fs < 0)
1090             fs = TextMedia::defaultFontSize ();
1091         int par_extra = SMIL::id_node_p == flow->id
1092             ? (int)(scale * fs) : 0;
1093         voffset += par_extra;
1094 
1095         SmilTextInfo saved_info = info;
1096         if (new_block)
1097             push ();
1098 
1099         info.props.mask (flow->props);
1100         if ((float)info.props.font_size.size () > max_font_size)
1101             max_font_size = info.props.font_size.size ();
1102         info.span (scale);
1103 
1104         if (flow->firstChild ())
1105             flow->firstChild ()->accept (this);
1106 
1107         if (rich_text.isEmpty ())
1108             par_extra = 0;
1109         if (new_block && flow->firstChild ())
1110             push ();
1111         voffset += par_extra;
1112 
1113         info = saved_info;
1114     }
1115     if (flow->nextSibling ())
1116         flow->nextSibling ()->accept (this);
1117 }
1118 
1119 void SmilTextVisitor::visit (SMIL::TemporalMoment *tm) {
1120     if (tm->state >= Node::state_began
1121             && tm->nextSibling ())
1122         tm->nextSibling ()->accept (this);
1123 }
1124 
1125 void CairoPaintVisitor::visit (SMIL::SmilText *txt) {
1126     Surface *s = txt->surface ();
1127     if (!s)
1128         return;
1129 
1130     SRect rect = s->bounds;
1131     IRect scr = matrix.toScreen (rect);
1132 
1133     if (!s->surface) {
1134 
1135         int w = scr.width ();
1136         float scale = 1.0 * w / (double)s->bounds.width ();
1137         SmilTextVisitor info (w, scale, txt->props);
1138 
1139         Node *first = txt->firstChild ();
1140         for (Node *n = first; n; n = n->nextSibling ())
1141             if (SMIL::id_node_clear == n->id) {
1142                 if (n->state >= Node::state_began)
1143                     first = n->nextSibling ();
1144                 else
1145                     break;
1146             }
1147         if (first)
1148             first->accept (&info);
1149 
1150         info.push ();
1151         if (info.first) {
1152             cairo_t *cr_txt = createContext (cairo_surface, s, (int) w, info.voffset);
1153 
1154             CAIRO_SET_SOURCE_RGB (cr_txt, 0);
1155             SmilTextBlock *b = info.first;
1156             int hoff = 0;
1157             int voff = 0;
1158             while (b) {
1159                 cairo_translate (cr_txt, b->rect.x() - hoff, b->rect.y() - voff);
1160                 QTextDocument td;
1161                 td.setDocumentMargin (0);
1162                 td.setDefaultFont (b->font);
1163                 bool have_alpha = (s->background_color & 0xff000000) < 0xff000000;
1164                 QImage img (QSize (b->rect.width(), b->rect.height()), have_alpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
1165                 img.fill (s->background_color);
1166                 td.setPageSize (QSize (b->rect.width(), b->rect.height() + 10));
1167                 setAlignment (td, b->align);
1168                 td.documentLayout()->setPaintDevice (&img);
1169                 td.setHtml (b->rich_text);
1170                 QPainter painter;
1171                 painter.begin (&img);
1172                 QAbstractTextDocumentLayout::PaintContext ctx;
1173                 ctx.clip = QRect (QPoint (0, 0), img.size ());
1174                 td.documentLayout()->draw (&painter, ctx);
1175                 painter.end();
1176 
1177                 cairo_surface_t *src_sf = cairo_image_surface_create_for_data (
1178                         img.bits (),
1179                         have_alpha ? CAIRO_FORMAT_ARGB32:CAIRO_FORMAT_RGB24,
1180                         img.width(), img.height(), img.bytesPerLine ());
1181                 cairo_pattern_t *pat = cairo_pattern_create_for_surface (src_sf);
1182                 cairo_pattern_set_extend (pat, CAIRO_EXTEND_NONE);
1183                 cairo_set_operator (cr_txt, CAIRO_OPERATOR_SOURCE);
1184                 cairo_set_source (cr_txt, pat);
1185                 cairo_rectangle (cr_txt, 0, 0, b->rect.width(), b->rect.height());
1186                 cairo_fill (cr_txt);
1187                 cairo_pattern_destroy (pat);
1188                 cairo_surface_destroy (src_sf);
1189 
1190                 hoff = b->rect.x ();
1191                 voff = b->rect.y ();
1192                 SmilTextBlock *tmp = b;
1193                 b = b->next;
1194                 delete tmp;
1195             }
1196             cairo_destroy (cr_txt);
1197 
1198             // update bounds rect
1199             s->bounds = matrix.toUser (IRect (scr.point, ISize (w, info.voffset)));
1200             txt->size = s->bounds.size;
1201             txt->updateBounds (false);
1202 
1203             // update coord. for painting below
1204             scr = matrix.toScreen (s->bounds);
1205         }
1206     }
1207     IRect clip_rect = clip.intersect (scr);
1208     if (s->surface && !clip_rect.isEmpty ())
1209         paint (&txt->transition, txt->media_opacity, s, scr.point, clip_rect);
1210     s->dirty = false;
1211 }
1212 
1213 void CairoPaintVisitor::visit (RP::Imfl * imfl) {
1214     if (imfl->surface ()) {
1215         cairo_save (cr);
1216         Matrix m = matrix;
1217         IRect scr = matrix.toScreen (SRect (0, 0, imfl->rp_surface->bounds.size));
1218         int w = scr.width ();
1219         int h = scr.height ();
1220         cairo_rectangle (cr, scr.x (), scr.y (), w, h);
1221         //cairo_clip (cr);
1222         cairo_translate (cr, scr.x (), scr.y ());
1223         cairo_scale (cr, 1.0*w/(double)imfl->size.width, 1.0*h/(double)imfl->size.height);
1224         if (imfl->needs_scene_img)
1225             cairo_push_group (cr);
1226         for (NodePtr n = imfl->firstChild (); n; n = n->nextSibling ())
1227             if (n->state >= Node::state_began &&
1228                     n->state < Node::state_deactivated) {
1229                 RP::TimingsBase * tb = convertNode<RP::TimingsBase>(n);
1230                 switch (n->id) {
1231                     case RP::id_node_viewchange:
1232                         if (!(int)tb->srcw)
1233                             tb->srcw = imfl->size.width;
1234                         if (!(int)tb->srch)
1235                             tb->srch = imfl->size.height;
1236                         // fall through
1237                     case RP::id_node_crossfade:
1238                     case RP::id_node_fadein:
1239                     case RP::id_node_fadeout:
1240                     case RP::id_node_fill:
1241                     case RP::id_node_wipe:
1242                         if (!(int)tb->w)
1243                             tb->w = imfl->size.width;
1244                         if (!(int)tb->h)
1245                             tb->h = imfl->size.height;
1246                         n->accept (this);
1247                         break;
1248                 }
1249             }
1250         if (imfl->needs_scene_img) {
1251             cairo_pattern_t * pat = cairo_pop_group (cr);
1252             cairo_pattern_set_extend (pat, CAIRO_EXTEND_NONE);
1253             cairo_set_source (cr, pat);
1254             cairo_paint (cr);
1255             cairo_pattern_destroy (pat);
1256         }
1257         cairo_restore (cr);
1258         matrix = m;
1259     }
1260 }
1261 
1262 void CairoPaintVisitor::visit (RP::Fill * fi) {
1263     CAIRO_SET_SOURCE_RGB (cr, fi->color);
1264     if ((int)fi->w && (int)fi->h) {
1265         cairo_rectangle (cr, fi->x, fi->y, fi->w, fi->h);
1266         cairo_fill (cr);
1267     }
1268 }
1269 
1270 void CairoPaintVisitor::visit (RP::Fadein * fi) {
1271     if (fi->target && fi->target->id == RP::id_node_image) {
1272         RP::Image *img = convertNode <RP::Image> (fi->target);
1273         ImageMedia *im = img && img->media_info
1274             ? static_cast <ImageMedia*> (img->media_info->media) : nullptr;
1275         if (im && img->surface ()) {
1276             Single sx = fi->srcx, sy = fi->srcy, sw = fi->srcw, sh = fi->srch;
1277             if (!(int)sw)
1278                 sw = img->size.width;
1279             if (!(int)sh)
1280                 sh = img->size.height;
1281             if ((int)fi->w && (int)fi->h && (int)sw && (int)sh) {
1282                 if (!img->img_surface->surface)
1283                     im->cached_img->copyImage (img->img_surface,
1284                             img->size, cairo_surface);
1285                 cairo_matrix_t matrix;
1286                 cairo_matrix_init_identity (&matrix);
1287                 float scalex = 1.0 * sw / fi->w;
1288                 float scaley = 1.0 * sh / fi->h;
1289                 cairo_matrix_scale (&matrix, scalex, scaley);
1290                 cairo_matrix_translate (&matrix,
1291                         1.0*sx/scalex - (double)fi->x,
1292                         1.0*sy/scaley - (double)fi->y);
1293                 cairo_save (cr);
1294                 cairo_rectangle (cr, fi->x, fi->y, fi->w, fi->h);
1295                 cairo_pattern_t *pat = cairo_pattern_create_for_surface (img->img_surface->surface);
1296                 cairo_pattern_set_extend (pat, CAIRO_EXTEND_NONE);
1297                 cairo_pattern_set_matrix (pat, &matrix);
1298                 cairo_set_source (cr, pat);
1299                 cairo_clip (cr);
1300                 cairo_paint_with_alpha (cr, 1.0 * fi->progress / 100);
1301                 cairo_restore (cr);
1302                 cairo_pattern_destroy (pat);
1303             }
1304         }
1305     }
1306 }
1307 
1308 void CairoPaintVisitor::visit (RP::Fadeout * fo) {
1309     if (fo->progress > 0) {
1310         CAIRO_SET_SOURCE_RGB (cr, fo->to_color);
1311         if ((int)fo->w && (int)fo->h) {
1312             cairo_save (cr);
1313             cairo_rectangle (cr, fo->x, fo->y, fo->w, fo->h);
1314             cairo_clip (cr);
1315             cairo_paint_with_alpha (cr, 1.0 * fo->progress / 100);
1316             cairo_restore (cr);
1317         }
1318     }
1319 }
1320 
1321 void CairoPaintVisitor::visit (RP::Crossfade * cf) {
1322     if (cf->target && cf->target->id == RP::id_node_image) {
1323         RP::Image *img = convertNode <RP::Image> (cf->target);
1324         ImageMedia *im = img && img->media_info
1325             ? static_cast <ImageMedia*> (img->media_info->media) : nullptr;
1326         if (im && img->surface ()) {
1327             Single sx = cf->srcx, sy = cf->srcy, sw = cf->srcw, sh = cf->srch;
1328             if (!(int)sw)
1329                 sw = img->size.width;
1330             if (!(int)sh)
1331                 sh = img->size.height;
1332             if ((int)cf->w && (int)cf->h && (int)sw && (int)sh) {
1333                 if (!img->img_surface->surface)
1334                     im->cached_img->copyImage (img->img_surface,
1335                             img->size, cairo_surface);
1336                 cairo_save (cr);
1337                 cairo_matrix_t matrix;
1338                 cairo_matrix_init_identity (&matrix);
1339                 float scalex = 1.0 * sw / cf->w;
1340                 float scaley = 1.0 * sh / cf->h;
1341                 cairo_matrix_scale (&matrix, scalex, scaley);
1342                 cairo_matrix_translate (&matrix,
1343                         1.0*sx/scalex - (double)cf->x,
1344                         1.0*sy/scaley - (double)cf->y);
1345                 cairo_rectangle (cr, cf->x, cf->y, cf->w, cf->h);
1346                 cairo_pattern_t *pat = cairo_pattern_create_for_surface (img->img_surface->surface);
1347                 cairo_pattern_set_extend (pat, CAIRO_EXTEND_NONE);
1348                 cairo_pattern_set_matrix (pat, &matrix);
1349                 cairo_set_source (cr, pat);
1350                 cairo_clip (cr);
1351                 cairo_paint_with_alpha (cr, 1.0 * cf->progress / 100);
1352                 cairo_restore (cr);
1353                 cairo_pattern_destroy (pat);
1354             }
1355         }
1356     }
1357 }
1358 
1359 void CairoPaintVisitor::visit (RP::Wipe * wipe) {
1360     if (wipe->target && wipe->target->id == RP::id_node_image) {
1361         RP::Image *img = convertNode <RP::Image> (wipe->target);
1362         ImageMedia *im = img && img->media_info
1363             ? static_cast <ImageMedia*> (img->media_info->media) : nullptr;
1364         if (im && img->surface ()) {
1365             Single x = wipe->x, y = wipe->y;
1366             Single tx = x, ty = y;
1367             Single w = wipe->w, h = wipe->h;
1368             Single sx = wipe->srcx, sy = wipe->srcy, sw = wipe->srcw, sh = wipe->srch;
1369             if (!(int)sw)
1370                 sw = img->size.width;
1371             if (!(int)sh)
1372                 sh = img->size.height;
1373             if (wipe->direction == RP::Wipe::dir_right) {
1374                 Single dx = w * 1.0 * wipe->progress / 100;
1375                 tx = x -w + dx;
1376                 w = dx;
1377             } else if (wipe->direction == RP::Wipe::dir_left) {
1378                 Single dx = w * 1.0 * wipe->progress / 100;
1379                 tx = x + w - dx;
1380                 x = tx;
1381                 w = dx;
1382             } else if (wipe->direction == RP::Wipe::dir_down) {
1383                 Single dy = h * 1.0 * wipe->progress / 100;
1384                 ty = y - h + dy;
1385                 h = dy;
1386             } else if (wipe->direction == RP::Wipe::dir_up) {
1387                 Single dy = h * 1.0 * wipe->progress / 100;
1388                 ty = y + h - dy;
1389                 y = ty;
1390                 h = dy;
1391             }
1392 
1393             if ((int)w && (int)h) {
1394                 if (!img->img_surface->surface)
1395                     im->cached_img->copyImage (img->img_surface,
1396                             img->size, cairo_surface);
1397                 cairo_matrix_t matrix;
1398                 cairo_matrix_init_identity (&matrix);
1399                 float scalex = 1.0 * sw / wipe->w;
1400                 float scaley = 1.0 * sh / wipe->h;
1401                 cairo_matrix_scale (&matrix, scalex, scaley);
1402                 cairo_matrix_translate (&matrix,
1403                         1.0*sx/scalex - (double)tx,
1404                         1.0*sy/scaley - (double)ty);
1405                 cairo_pattern_t *pat = cairo_pattern_create_for_surface (img->img_surface->surface);
1406                 cairo_pattern_set_extend (pat, CAIRO_EXTEND_NONE);
1407                 cairo_pattern_set_matrix (pat, &matrix);
1408                 cairo_set_source (cr, pat);
1409                 cairo_rectangle (cr, x, y, w, h);
1410                 cairo_fill (cr);
1411                 cairo_pattern_destroy (pat);
1412             }
1413         }
1414     }
1415 }
1416 
1417 void CairoPaintVisitor::visit (RP::ViewChange * vc) {
1418     if (vc->unfinished () || vc->progress < 100) {
1419         cairo_pattern_t * pat = cairo_pop_group (cr); // from imfl
1420         cairo_pattern_set_extend (pat, CAIRO_EXTEND_NONE);
1421         cairo_push_group (cr);
1422         cairo_save (cr);
1423         cairo_set_source (cr, pat);
1424         cairo_paint (cr);
1425         if ((int)vc->w && (int)vc->h && (int)vc->srcw && (int)vc->srch) {
1426             cairo_matrix_t matrix;
1427             cairo_matrix_init_identity (&matrix);
1428             float scalex = 1.0 * vc->srcw / vc->w;
1429             float scaley = 1.0 * vc->srch / vc->h;
1430             cairo_matrix_scale (&matrix, scalex, scaley);
1431             cairo_matrix_translate (&matrix,
1432                     1.0*vc->srcx/scalex - (double)vc->x,
1433                     1.0*vc->srcy/scaley - (double)vc->y);
1434             cairo_pattern_set_matrix (pat, &matrix);
1435             cairo_set_source (cr, pat);
1436             cairo_rectangle (cr, vc->x, vc->y, vc->w, vc->h);
1437             cairo_fill (cr);
1438         }
1439         cairo_pattern_destroy (pat);
1440         cairo_restore (cr);
1441     }
1442 }
1443 
1444 #endif
1445 
1446 //-----------------------------------------------------------------------------
1447 
1448 namespace KMPlayer {
1449 
1450 class MouseVisitor : public Visitor
1451 {
1452     ViewArea *view_area;
1453     Matrix matrix;
1454     NodePtrW source;
1455     const MessageType event;
1456     int x, y;
1457     bool handled;
1458     bool bubble_up;
1459 
1460     bool deliverAndForward (Node *n, Surface *s, bool inside, bool deliver);
1461     void surfaceEvent (Node *mt, Surface *s);
1462 public:
1463     MouseVisitor (ViewArea *v, MessageType evt, Matrix m, int x, int y);
1464     ~MouseVisitor () override {}
1465     using Visitor::visit;
1466     void visit (Node * n) override;
1467     void visit (Element *) override;
1468     void visit (SMIL::Smil *) override;
1469     void visit (SMIL::Layout *) override;
1470     void visit (SMIL::RegionBase *) override;
1471     void visit (SMIL::MediaType * n) override;
1472     void visit (SMIL::SmilText * n) override;
1473     void visit (SMIL::Anchor *) override;
1474     void visit (SMIL::Area *) override;
1475     QCursor cursor;
1476 };
1477 
1478 } // namespace
1479 
1480 MouseVisitor::MouseVisitor (ViewArea *v, MessageType evt, Matrix m, int a, int b)
1481   : view_area (v), matrix (m), event (evt), x (a), y (b),
1482     handled (false), bubble_up (false) {
1483 }
1484 
1485 void MouseVisitor::visit (Node * n) {
1486     qCDebug(LOG_KMPLAYER_COMMON) << "Mouse event ignored for " << n->nodeName ();
1487 }
1488 
1489 void MouseVisitor::visit (SMIL::Smil *s) {
1490     if (s->active () && s->layout_node)
1491         s->layout_node->accept (this);
1492 }
1493 
1494 void MouseVisitor::visit (SMIL::Layout * layout) {
1495     if (layout->root_layout)
1496         layout->root_layout->accept (this);
1497 }
1498 
1499 void MouseVisitor::visit (SMIL::RegionBase *region) {
1500     Surface *s = (Surface *) region->role (RoleDisplay);
1501     if (s) {
1502         SRect rect = s->bounds;
1503         IRect scr = matrix.toScreen (rect);
1504         int rx = scr.x(), ry = scr.y(), rw = scr.width(), rh = scr.height();
1505         handled = false;
1506         bool inside = x > rx && x < rx+rw && y > ry && y< ry+rh;
1507         if (!inside && (event == MsgEventClicked || !s->has_mouse))
1508             return;
1509 
1510         if (event == MsgEventClicked && !s->virtual_size.isEmpty () &&
1511                 x > rx + rw - REGION_SCROLLBAR_WIDTH) {
1512             const int sbh = rh - REGION_SCROLLBAR_WIDTH;
1513             const int vy = s->virtual_size.height;
1514             const int knob_h = sbh * rh / vy;
1515             int knob_y = y - ry - 0.5 * knob_h;
1516             if (knob_y < 0)
1517                 knob_y = 0;
1518             else if (knob_y + knob_h > sbh)
1519                 knob_y = sbh - knob_h;
1520             s->y_scroll = vy * knob_y / sbh;
1521             view_area->scheduleRepaint (scr);
1522             return;
1523         }
1524         if (event == MsgEventClicked && !s->virtual_size.isEmpty () &&
1525                 y > ry + rh - REGION_SCROLLBAR_WIDTH) {
1526             const int sbw = rw - REGION_SCROLLBAR_WIDTH;
1527             const int vw = s->virtual_size.width;
1528             const int knob_w = sbw * rw / vw;
1529             int knob_x = x - rx - 0.5 * knob_w;
1530             if (knob_x < 0)
1531                 knob_x = 0;
1532             else if (knob_x + knob_w > sbw)
1533                 knob_x = sbw - knob_w;
1534             s->x_scroll = vw * knob_x / sbw;
1535             view_area->scheduleRepaint (scr);
1536             return;
1537         }
1538 
1539         NodePtrW src = source;
1540         source = region;
1541         Matrix m = matrix;
1542         matrix = Matrix (rect.x(), rect.y(), 1.0, 1.0);
1543         matrix.transform (m);
1544         if (!s->virtual_size.isEmpty ())
1545             matrix.translate (-s->x_scroll, -s->y_scroll);
1546         bubble_up = false;
1547 
1548         bool child_handled = false;
1549         if (inside || s->has_mouse)
1550             for (SurfacePtr c = s->firstChild (); c; c = c->nextSibling ()) {
1551                 if (c->node && c->node->id == SMIL::id_node_region) {
1552                     c->node->accept (this);
1553                     child_handled |= handled;
1554                     if (!source || !source->active ())
1555                         break;
1556                 } else {
1557                     break;
1558                 }
1559             }
1560         child_handled &= !bubble_up;
1561         bubble_up = false;
1562         if (source && source->active ())
1563             deliverAndForward (region, s, inside, !child_handled);
1564 
1565         handled = inside;
1566         matrix = m;
1567         source = src;
1568     }
1569 }
1570 
1571 static void followLink (SMIL::LinkingBase * link) {
1572     qCDebug(LOG_KMPLAYER_COMMON) << "link to " << link->href << " clicked";
1573     if (link->href.startsWith ("#")) {
1574         SMIL::Smil * s = SMIL::Smil::findSmilNode (link);
1575         if (s)
1576             s->jump (link->href.mid (1));
1577         else
1578             qCCritical(LOG_KMPLAYER_COMMON) << "In document jumps smil not found" << endl;
1579     } else {
1580         PlayListNotify *notify = link->document ()->notify_listener;
1581         if (notify && !link->target.isEmpty ()) {
1582             notify->openUrl(QUrl(link->href), link->target, QString());
1583         } else {
1584             NodePtr n = link;
1585             for (NodePtr p = link->parentNode (); p; p = p->parentNode ()) {
1586                 if (n->mrl () && n->mrl ()->opener == p) {
1587                     p->setState (Node::state_deferred);
1588                     p->mrl ()->setParam (Ids::attr_src, link->href, nullptr);
1589                     p->activate ();
1590                     break;
1591                 }
1592                 n = p;
1593             }
1594         }
1595     }
1596 }
1597 
1598 void MouseVisitor::visit (SMIL::Anchor * anchor) {
1599     if (event == MsgEventPointerMoved)
1600         cursor.setShape (Qt::PointingHandCursor);
1601     else if (event == MsgEventClicked)
1602         followLink (anchor);
1603 }
1604 
1605 void MouseVisitor::visit (SMIL::Area * area) {
1606     NodePtr n = area->parentNode ();
1607     Surface *s = (Surface *) n->role (RoleDisplay);
1608     if (s) {
1609         SRect rect = s->bounds;
1610         IRect scr = matrix.toScreen (rect);
1611         int w = scr.width (), h = scr.height ();
1612         if (area->nr_coords > 1) {
1613             Single left = area->coords[0].size (rect.width ());
1614             Single top = area->coords[1].size (rect.height ());
1615             matrix.getXY (left, top);
1616             if (x < left || x > left + w || y < top || y > top + h)
1617                 return;
1618             if (area->nr_coords > 3) {
1619                 Single right = area->coords[2].size (rect.width ());
1620                 Single bottom = area->coords[3].size (rect.height ());
1621                 matrix.getXY (right, bottom);
1622                 if (x > right || y > bottom)
1623                     return;
1624             }
1625         }
1626         if (event == MsgEventPointerMoved)
1627             cursor.setShape (Qt::PointingHandCursor);
1628         else {
1629             ConnectionList *nl = nodeMessageReceivers (area, event);
1630             if (nl)
1631                 for (Connection *c = nl->first(); c; c = nl->next ()) {
1632                     if (c->connecter)
1633                         c->connecter->accept (this);
1634                     if (!source || !source->active ())
1635                         return;
1636                 }
1637             if (event == MsgEventClicked && !area->href.isEmpty ())
1638                 followLink (area);
1639         }
1640     }
1641 }
1642 
1643 void MouseVisitor::visit (Element *elm) {
1644     Runtime *rt = (Runtime *) elm->role (RoleTiming);
1645     if (rt) {
1646         Posting mouse_event (source, event);
1647         rt->message (event, &mouse_event);
1648     }
1649 }
1650 
1651 bool MouseVisitor::deliverAndForward (Node *node, Surface *s, bool inside, bool deliver) {
1652     bool forward = deliver;
1653     MessageType user_event = event;
1654     if (event == MsgEventPointerMoved) {
1655         forward = true; // always pass move events
1656         if (inside && !s->has_mouse) {
1657             deliver = true;
1658             user_event = MsgEventPointerInBounds;
1659         } else if (!inside && s->has_mouse) {
1660             deliver = true;
1661             user_event = MsgEventPointerOutBounds;
1662         } else if (!inside) {
1663             return false;
1664         } else {
1665             deliver = false;
1666         }
1667     }
1668     s->has_mouse = inside;
1669 
1670     if (event != MsgEventPointerMoved && !inside)
1671         return false;
1672 
1673     NodePtrW node_save = node;
1674 
1675     if (forward) {
1676         ConnectionList *nl = nodeMessageReceivers (node, MsgSurfaceAttach);
1677         if (nl) {
1678             NodePtr node_save = source;
1679             source = node;
1680 
1681             for (Connection *c = nl->first(); c; c = nl->next ()) {
1682                 if (c->connecter)
1683                     c->connecter->accept (this);
1684                 if (!source || !source->active ())
1685                     break;
1686             }
1687             source = node_save;
1688         }
1689     }
1690     if (!node_save || !node->active ())
1691         return false;
1692     if (deliver) {
1693         Posting mouse_event (node, user_event);
1694         node->deliver (user_event, &mouse_event);
1695     }
1696     if (!node_save || !node->active ())
1697         return false;
1698     return true;
1699 }
1700 
1701 void MouseVisitor::surfaceEvent (Node *node, Surface *s) {
1702     if (!s)
1703         return;
1704     if (s->node && s->node.ptr () != node) {
1705         s->node->accept (this);
1706         return;
1707     }
1708     SRect rect = s->bounds;
1709     IRect scr = matrix.toScreen (rect);
1710     int rx = scr.x(), ry = scr.y(), rw = scr.width(), rh = scr.height();
1711     const bool inside = x > rx && x < rx+rw && y > ry && y< ry+rh;
1712     const bool had_mouse = s->has_mouse;
1713     if (deliverAndForward (node, s, inside, true) &&
1714             (inside || had_mouse) &&
1715             s->firstChild () && s->firstChild ()->node) {
1716         Matrix m = matrix;
1717         matrix = Matrix (rect.x(), rect.y(), s->xscale, s->yscale);
1718         matrix.transform (m);
1719         s->firstChild ()->node->accept (this);
1720         matrix = m;
1721     }
1722 }
1723 
1724 void MouseVisitor::visit (SMIL::MediaType *mt) {
1725     if (mt->sensitivity == SMIL::MediaType::sens_transparent)
1726         bubble_up = true;
1727     else
1728         surfaceEvent (mt, mt->surface ());
1729 }
1730 
1731 void MouseVisitor::visit (SMIL::SmilText *st) {
1732     surfaceEvent (st, st->surface ());
1733 }
1734 
1735 //-----------------------------------------------------------------------------
1736 
1737 namespace KMPlayer {
1738 class ViewerAreaPrivate
1739 {
1740 public:
1741     ViewerAreaPrivate (ViewArea *v)
1742         : m_view_area (v), backing_store (0), gc(0),
1743           screen(nullptr), visual(nullptr), width(0), height(0)
1744     {}
1745     ~ViewerAreaPrivate() {
1746         destroyBackingStore ();
1747         if (gc) {
1748             xcb_connection_t* connection = QX11Info::connection();
1749             xcb_free_gc(connection, gc);
1750         }
1751     }
1752     void clearSurface (Surface *s) {
1753 #ifdef KMPLAYER_WITH_CAIRO
1754         if (s->surface) {
1755             cairo_surface_destroy (s->surface);
1756             s->surface = nullptr;
1757         }
1758         destroyBackingStore ();
1759 #endif
1760     }
1761     void resizeSurface (Surface *s) {
1762 #ifdef KMPLAYER_WITH_CAIRO
1763         int w = (int)(m_view_area->width() * m_view_area->devicePixelRatioF());
1764         int h = (int)(m_view_area->height() * m_view_area->devicePixelRatioF());
1765         if ((w != width || h != height) && s->surface) {
1766             clearSurface (s);
1767             width = w;
1768             height = h;
1769         }
1770 #endif
1771     }
1772 #ifdef KMPLAYER_WITH_CAIRO
1773     cairo_surface_t *createSurface (int w, int h) {
1774         xcb_connection_t* connection = QX11Info::connection();
1775         destroyBackingStore ();
1776         xcb_screen_t* scr = screen_of_display(connection, QX11Info::appScreen());
1777         backing_store = xcb_generate_id(connection);
1778         xcb_void_cookie_t cookie = xcb_create_pixmap_checked(connection, scr->root_depth, backing_store, m_view_area->winId(), w, h);
1779         xcb_generic_error_t* error = xcb_request_check(connection, cookie);
1780         if (error) {
1781             qCDebug(LOG_KMPLAYER_COMMON) << "failed to create pixmap";
1782             return nullptr;
1783         }
1784         return cairo_xcb_surface_create(connection, backing_store, visual_of_screen(connection, scr), w, h);
1785     }
1786     void swapBuffer (const IRect &sr, int dx, int dy) {
1787         xcb_connection_t* connection = QX11Info::connection();
1788         if (!gc) {
1789             gc = xcb_generate_id(connection);
1790             uint32_t values[] = { XCB_GX_COPY, XCB_FILL_STYLE_SOLID,
1791                 XCB_SUBWINDOW_MODE_CLIP_BY_CHILDREN, 0 };
1792             xcb_create_gc(connection, gc, backing_store,
1793                     XCB_GC_FUNCTION | XCB_GC_FILL_STYLE |
1794                     XCB_GC_SUBWINDOW_MODE | XCB_GC_GRAPHICS_EXPOSURES, values);
1795         }
1796         xcb_copy_area(connection, backing_store, m_view_area->winId(),
1797                 gc, sr.x(), sr.y(), dx, dy, sr.width (), sr.height ());
1798         xcb_flush(connection);
1799     }
1800 #endif
1801     void destroyBackingStore () {
1802 #ifdef KMPLAYER_WITH_CAIRO
1803         if (backing_store) {
1804             xcb_connection_t* connection = QX11Info::connection();
1805             xcb_free_pixmap(connection, backing_store);
1806         }
1807 #endif
1808         backing_store = 0;
1809     }
1810     xcb_screen_t *screen_of_display(xcb_connection_t* c, int num)
1811     {
1812         if (!screen) {
1813             xcb_screen_iterator_t iter;
1814 
1815             iter = xcb_setup_roots_iterator (xcb_get_setup (c));
1816             for (; iter.rem; --num, xcb_screen_next (&iter))
1817                 if (num == 0) {
1818                     screen = iter.data;
1819                     break;
1820                 }
1821         }
1822         return screen;
1823     }
1824 
1825     xcb_visualtype_t* visual_of_screen(xcb_connection_t* c, xcb_screen_t* screen)
1826     {
1827         Q_UNUSED(c)
1828 
1829         if (!visual) {
1830             xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator (screen);
1831             for (; depth_iter.rem; xcb_depth_next (&depth_iter)) {
1832                 xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator (depth_iter.data);
1833                 for (; visual_iter.rem; xcb_visualtype_next (&visual_iter)) {
1834                     if (screen->root_visual == visual_iter.data->visual_id) {
1835                         visual = visual_iter.data;
1836                         break;
1837                     }
1838                 }
1839             }
1840         }
1841         return visual;
1842     }
1843     ViewArea *m_view_area;
1844     xcb_drawable_t backing_store;
1845     xcb_gcontext_t gc;
1846     xcb_screen_t* screen;
1847     xcb_visualtype_t* visual;
1848     int width;
1849     int height;
1850 };
1851 
1852 class RepaintUpdater
1853 {
1854 public:
1855     RepaintUpdater (Node *n, RepaintUpdater *nx) : node (n), next (nx) {}
1856 
1857     NodePtrW node;
1858     RepaintUpdater *next;
1859 };
1860 
1861 }
1862 
1863 ViewArea::ViewArea (QWidget *, View * view, bool paint_bg)
1864 // : QWidget (parent, "kde_kmplayer_viewarea", WResizeNoErase | WRepaintNoErase),
1865  : //QWidget (parent),
1866    d (new ViewerAreaPrivate (this)),
1867    m_view (view),
1868    m_collection (new KActionCollection (this)),
1869    surface (new Surface (this)),
1870    m_mouse_invisible_timer (0),
1871    m_repaint_timer (0),
1872    m_restore_fullscreen_timer (0),
1873    m_fullscreen (false),
1874    m_minimal (false),
1875    m_updaters_enabled (true),
1876    m_paint_background (paint_bg) {
1877     if (!paint_bg)
1878         setAttribute (Qt::WA_NoSystemBackground, true);
1879     QPalette palette;
1880     palette.setColor (backgroundRole(), QColor (0, 0, 0));
1881     setPalette (palette);
1882     setAcceptDrops (true);
1883     //new KAction (i18n ("Fullscreen"), KShortcut (Qt::Key_F), this, SLOT (accelActivated ()), m_collection, "view_fullscreen_toggle");
1884     setMouseTracking (true);
1885     setFocusPolicy (Qt::ClickFocus);
1886     QCoreApplication::instance()->installNativeEventFilter(this);
1887 }
1888 
1889 ViewArea::~ViewArea () {
1890     delete d;
1891 }
1892 
1893 void ViewArea::stopTimers () {
1894     if (m_mouse_invisible_timer) {
1895         killTimer (m_mouse_invisible_timer);
1896         m_mouse_invisible_timer = 0;
1897     }
1898     if (m_repaint_timer) {
1899         killTimer (m_repaint_timer);
1900         m_repaint_timer = 0;
1901     }
1902 }
1903 
1904 void ViewArea::fullScreen () {
1905     stopTimers ();
1906     if (m_fullscreen) {
1907         setVisible(false);
1908         setWindowState(windowState() & ~Qt::WindowFullScreen); // reset
1909         if (!m_restore_fullscreen_timer)
1910             m_restore_fullscreen_timer = startTimer(25);
1911         for (int i = 0; i < m_collection->count (); ++i)
1912             m_collection->action (i)->setEnabled (false);
1913         m_view->controlPanel()->enableFullscreenButton(false);
1914         unsetCursor();
1915     } else {
1916         m_topwindow_rect = topLevelWidget ()->geometry ();
1917         m_view->dockArea()->takeCentralWidget();
1918         move(qApp->desktop()->screenGeometry(this).topLeft());
1919         setVisible(true);
1920         setWindowState( windowState() ^ Qt::WindowFullScreen ); // set
1921         for (int i = 0; i < m_collection->count (); ++i)
1922             m_collection->action (i)->setEnabled (true);
1923         m_view->controlPanel()->enableFullscreenButton(true);
1924         m_mouse_invisible_timer = startTimer(MOUSE_INVISIBLE_DELAY);
1925     }
1926     m_fullscreen = !m_fullscreen;
1927     m_view->controlPanel()->fullscreenAction->setChecked (m_fullscreen);
1928 
1929     d->clearSurface (surface.ptr ());
1930     Q_EMIT fullScreenChanged ();
1931 }
1932 
1933 void ViewArea::minimalMode () {
1934     m_minimal = !m_minimal;
1935     stopTimers ();
1936     m_mouse_invisible_timer = m_repaint_timer = 0;
1937     if (m_minimal) {
1938         m_view->setViewOnly ();
1939         m_view->setControlPanelMode (KMPlayer::View::CP_AutoHide);
1940         m_view->setNoInfoMessages (true);
1941         m_view->controlPanel()->enableFullscreenButton(true);
1942     } else {
1943         m_view->setControlPanelMode (KMPlayer::View::CP_Show);
1944         m_view->setNoInfoMessages (false);
1945         m_view->controlPanel()->enableFullscreenButton(false);
1946     }
1947     m_topwindow_rect = topLevelWidget ()->geometry ();
1948 }
1949 
1950 void ViewArea::accelActivated () {
1951     m_view->controlPanel()->fullscreenAction->trigger ();
1952 }
1953 
1954 void ViewArea::keyPressEvent (QKeyEvent *e) {
1955     if (surface->node) {
1956         QString txt = e->text ();
1957         if (!txt.isEmpty ())
1958             surface->node->document ()->message (MsgAccessKey,
1959                     (void *)(long) txt[0].unicode ());
1960     }
1961 }
1962 
1963 void ViewArea::mousePressEvent (QMouseEvent * e) {
1964     int devicex = (int)(e->x() * devicePixelRatioF());
1965     int devicey = (int)(e->y() * devicePixelRatioF());
1966     if (surface->node) {
1967         MouseVisitor visitor (this, MsgEventClicked,
1968                 Matrix (surface->bounds.x (), surface->bounds.y (),
1969                     surface->xscale, surface->yscale),
1970                 devicex, devicey);
1971         surface->node->accept (&visitor);
1972     }
1973 }
1974 
1975 void ViewArea::mouseDoubleClickEvent (QMouseEvent *) {
1976     m_view->fullScreen (); // screensaver stuff
1977 }
1978 
1979 void ViewArea::mouseMoveEvent (QMouseEvent * e) {
1980     if (e->buttons () == Qt::NoButton)
1981         m_view->mouseMoved (e->x (), e->y ());
1982     if (surface->node) {
1983         int devicex = (int)(e->x() * devicePixelRatioF());
1984         int devicey = (int)(e->y() * devicePixelRatioF());
1985         MouseVisitor visitor (this, MsgEventPointerMoved,
1986                 Matrix (surface->bounds.x (), surface->bounds.y (),
1987                     surface->xscale, surface->yscale),
1988                 devicex, devicey);
1989         surface->node->accept (&visitor);
1990         setCursor (visitor.cursor);
1991     }
1992     e->accept ();
1993     mouseMoved (); // for m_mouse_invisible_timer
1994 }
1995 
1996 void ViewArea::syncVisual () {
1997     pixel_device_ratio = devicePixelRatioF();
1998     int w = (int)(width() * devicePixelRatioF());
1999     int h = (int)(height() * devicePixelRatioF());
2000     IRect rect = m_repaint_rect.intersect (IRect (0, 0, w, h));
2001 #ifdef KMPLAYER_WITH_CAIRO
2002     if (surface->node) {
2003         int ex = rect.x ();
2004         if (ex > 0)
2005             ex--;
2006         int ey = rect.y ();
2007         if (ey > 0)
2008             ey--;
2009         int ew = rect.width () + 2;
2010         int eh = rect.height () + 2;
2011         IRect swap_rect;
2012         cairo_surface_t *merge = nullptr;
2013         cairo_pattern_t *pat = nullptr;
2014         cairo_t *cr = nullptr;
2015         if (!surface->surface) {
2016             surface->surface = d->createSurface(w, h);
2017             swap_rect = IRect (ex, ey, ew, eh);
2018             CairoPaintVisitor visitor (surface->surface,
2019                     Matrix (surface->bounds.x(), surface->bounds.y(),
2020                         surface->xscale, surface->yscale),
2021                     swap_rect,
2022                     palette ().color (backgroundRole ()), true);
2023             surface->node->accept (&visitor);
2024             m_update_rect = IRect ();
2025         } else if (!rect.isEmpty ()) {
2026             merge = cairo_surface_create_similar (surface->surface,
2027                     CAIRO_CONTENT_COLOR, ew, eh);
2028             {
2029                 CairoPaintVisitor visitor (merge,
2030                         Matrix (surface->bounds.x()-ex, surface->bounds.y()-ey,
2031                             surface->xscale, surface->yscale),
2032                         IRect (0, 0, ew, eh),
2033                         palette ().color (backgroundRole ()), true);
2034                 surface->node->accept (&visitor);
2035             }
2036             cr = cairo_create (surface->surface);
2037             pat = cairo_pattern_create_for_surface (merge);
2038             cairo_pattern_set_extend (pat, CAIRO_EXTEND_NONE);
2039             cairo_matrix_t mat;
2040             cairo_matrix_init_translate (&mat, (int) -ex, (int) -ey);
2041             cairo_pattern_set_matrix (pat, &mat);
2042             cairo_set_source (cr, pat);
2043             //cairo_set_operator (cr, CAIRO_OPERATOR_ADD);
2044             cairo_rectangle (cr, ex, ey, ew, eh);
2045             //cairo_fill (cr);
2046             cairo_clip (cr);
2047             cairo_paint_with_alpha (cr, .8);
2048             swap_rect = IRect (ex, ey, ew, eh).unite (m_update_rect);
2049             m_update_rect = IRect (ex, ey, ew, eh);
2050         } else {
2051             swap_rect = m_update_rect;
2052             m_update_rect = IRect ();
2053         }
2054         d->swapBuffer (swap_rect, swap_rect.x (), swap_rect.y ());
2055         if (merge) {
2056             cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
2057             cairo_rectangle (cr, ex, ey, ew, eh);
2058             cairo_fill (cr);
2059             cairo_destroy (cr);
2060             cairo_pattern_destroy (pat);
2061             cairo_surface_destroy (merge);
2062         }
2063         cairo_surface_flush (surface->surface);
2064     } else
2065 #endif
2066     {
2067         m_update_rect = IRect ();
2068         repaint(QRect(rect.x() / devicePixelRatioF(),
2069                       rect.y() / devicePixelRatioF(),
2070                       rect.width() / devicePixelRatioF(),
2071                       rect.height() / devicePixelRatioF()));
2072     }
2073 }
2074 
2075 void ViewArea::paintEvent (QPaintEvent * pe) {
2076 #ifdef KMPLAYER_WITH_CAIRO
2077     if (surface->node) {
2078         int x = (int)(pe->rect().x() * devicePixelRatioF());
2079         int y = (int)(pe->rect().y() * devicePixelRatioF());
2080         int w = (int)(pe->rect().width() * devicePixelRatioF());
2081         int h = (int)(pe->rect().height() * devicePixelRatioF());
2082         scheduleRepaint(IRect(x, y, w, h));
2083     } else
2084 #endif
2085         if (m_fullscreen || m_paint_background)
2086     {
2087         QPainter p (this);
2088         p.fillRect (pe->rect (), QBrush (palette ().color (backgroundRole ())));
2089         p.end ();
2090     }
2091 }
2092 
2093 QPaintEngine *ViewArea::paintEngine () const {
2094 #ifdef KMPLAYER_WITH_CAIRO
2095     if (surface->node)
2096         return nullptr;
2097     else
2098 #endif
2099         return QWidget::paintEngine ();
2100 }
2101 
2102 void ViewArea::scale (int) {
2103     resizeEvent (nullptr);
2104 }
2105 
2106 void ViewArea::updateSurfaceBounds () {
2107     int devicew = (int)(width() * devicePixelRatioF());
2108     int deviceh = (int)(height() * devicePixelRatioF());
2109     Single x, y, w = devicew, h = deviceh;
2110     h -= m_view->statusBarHeight ();
2111     h -= m_view->controlPanel ()->isVisible () && !m_fullscreen
2112         ? (m_view->controlPanelMode () == View::CP_Only
2113                 ? h
2114                 : (Single) m_view->controlPanel()->maximumSize ().height ())
2115         : Single (0);
2116 
2117     int scale = m_view->controlPanel ()->scale_slider->sliderPosition ();
2118     if (scale != 100) {
2119         int nw = w * 1.0 * scale / 100;
2120         int nh = h * 1.0 * scale / 100;
2121         x += (w - nw) / 2;
2122         y += (h - nh) / 2;
2123         w = nw;
2124         h = nh;
2125     }
2126     if (surface->node) {
2127         d->resizeSurface (surface.ptr ());
2128         surface->resize (SRect (x, y, w, h));
2129         surface->node->message (MsgSurfaceBoundsUpdate, (void *) true);
2130     }
2131     scheduleRepaint (IRect (0, 0, devicew, deviceh));
2132 }
2133 
2134 void ViewArea::resizeEvent (QResizeEvent *) {
2135     if (!m_view->controlPanel ()) return;
2136     Single x, y, w = width (), h = height ();
2137     Single hsb = m_view->statusBarHeight ();
2138     int hcp = m_view->controlPanel ()->isVisible ()
2139         ? (m_view->controlPanelMode () == View::CP_Only
2140                 ? h-hsb
2141                 : (Single) m_view->controlPanel()->maximumSize ().height ())
2142         : Single (0);
2143     // move controlpanel over video when autohiding and playing
2144     bool auto_hide = m_view->controlPanelMode () == View::CP_AutoHide;
2145     h -= Single (auto_hide ? 0 : hcp) - hsb;
2146     // now scale the regions and check if video region is already sized
2147     updateSurfaceBounds ();
2148 
2149     // finally resize controlpanel and video widget
2150     if (m_view->controlPanel ()->isVisible ())
2151         m_view->controlPanel ()->setGeometry (0, h-(auto_hide ? hcp:0), w, hcp);
2152     if (m_view->statusBar ()->isVisible ())
2153         m_view->statusBar ()->setGeometry (0, h-hsb, w, hsb);
2154     int scale = m_view->controlPanel ()->scale_slider->sliderPosition ();
2155     Single ws = w * scale / 100;
2156     Single hs = h * scale / 100;
2157     x += (w - ws) / 2;
2158     y += (h - hs) / 2;
2159     m_view->console ()->setGeometry (0, 0, w, h);
2160     m_view->picture ()->setGeometry (0, 0, w, h);
2161     if (!surface->node && video_widgets.size () == 1) {
2162         x *= devicePixelRatioF();
2163         y *= devicePixelRatioF();
2164         ws *= devicePixelRatioF();
2165         hs *= devicePixelRatioF();
2166         video_widgets.first ()->setGeometry (IRect (x, y, ws, hs));
2167     }
2168 }
2169 
2170 Surface *ViewArea::getSurface (Mrl *mrl) {
2171     surface->clear ();
2172     surface->node = mrl;
2173     qCDebug(LOG_KMPLAYER_COMMON) << mrl;
2174     //m_view->viewer()->resetBackgroundColor ();
2175     if (mrl) {
2176         updateSurfaceBounds ();
2177 #ifdef KMPLAYER_WITH_CAIRO
2178         setAttribute (Qt::WA_OpaquePaintEvent, true);
2179         setAttribute (Qt::WA_PaintOnScreen, true);
2180 #endif
2181         return surface.ptr ();
2182     } else {
2183 #ifdef KMPLAYER_WITH_CAIRO
2184         setAttribute (Qt::WA_OpaquePaintEvent, false);
2185         setAttribute (Qt::WA_PaintOnScreen, false);
2186         d->clearSurface (surface.ptr ());
2187 #endif
2188     }
2189     int devicew = (int)(width() * devicePixelRatioF());
2190     int deviceh = (int)(height() * devicePixelRatioF());
2191     scheduleRepaint (IRect (0, 0, devicew, deviceh));
2192     return nullptr;
2193 }
2194 
2195 void ViewArea::showEvent (QShowEvent *) {
2196     resizeEvent (nullptr);
2197 }
2198 
2199 void ViewArea::dropEvent (QDropEvent * de) {
2200     m_view->dropEvent (de);
2201 }
2202 
2203 void ViewArea::dragEnterEvent (QDragEnterEvent* dee) {
2204     m_view->dragEnterEvent (dee);
2205 }
2206 
2207 void ViewArea::contextMenuEvent (QContextMenuEvent * e) {
2208     m_view->controlPanel ()->popupMenu->exec (e->globalPos ());
2209 }
2210 
2211 void ViewArea::mouseMoved () {
2212     if (m_fullscreen) {
2213         if (m_mouse_invisible_timer)
2214             killTimer (m_mouse_invisible_timer);
2215         unsetCursor ();
2216         m_mouse_invisible_timer = startTimer (MOUSE_INVISIBLE_DELAY);
2217     }
2218 }
2219 
2220 void ViewArea::scheduleRepaint (const IRect &rect) {
2221     if (m_repaint_timer) {
2222         m_repaint_rect = m_repaint_rect.unite (rect);
2223     } else {
2224         m_repaint_rect = rect;
2225         m_repaint_timer = startTimer (25);
2226     }
2227 }
2228 
2229 ConnectionList *ViewArea::updaters () {
2230     if (!m_repaint_timer)
2231         m_repaint_timer = startTimer (25);
2232     return &m_updaters;
2233 }
2234 
2235 void ViewArea::enableUpdaters (bool enable, unsigned int skip) {
2236     m_updaters_enabled = enable;
2237     Connection *connect = m_updaters.first ();
2238     if (enable && connect) {
2239         UpdateEvent event (connect->connecter->document (), skip);
2240         for (; connect; connect = m_updaters.next ())
2241             if (connect->connecter)
2242                 connect->connecter->message (MsgSurfaceUpdate, &event);
2243         if (!m_repaint_timer)
2244             m_repaint_timer = startTimer (25);
2245     } else if (!enable && m_repaint_timer &&
2246             m_repaint_rect.isEmpty () && m_update_rect.isEmpty ()) {
2247         killTimer (m_repaint_timer);
2248         m_repaint_timer = 0;
2249     }
2250 }
2251 
2252 void ViewArea::timerEvent (QTimerEvent * e) {
2253     if (e->timerId () == m_mouse_invisible_timer) {
2254         killTimer (m_mouse_invisible_timer);
2255         m_mouse_invisible_timer = 0;
2256         if (m_fullscreen)
2257             setCursor (QCursor (Qt::BlankCursor));
2258     } else if (e->timerId () == m_repaint_timer) {
2259         Connection *connect = m_updaters.first ();
2260         int count = 0;
2261         if (m_updaters_enabled && connect) {
2262             UpdateEvent event (connect->connecter->document (), 0);
2263             for (; connect; count++, connect = m_updaters.next ())
2264                 if (connect->connecter)
2265                     connect->connecter->message (MsgSurfaceUpdate, &event);
2266         }
2267         //repaint (m_repaint_rect, false);
2268         if (!m_repaint_rect.isEmpty () || !m_update_rect.isEmpty ()) {
2269             syncVisual ();
2270             m_repaint_rect = IRect ();
2271         }
2272         if (m_update_rect.isEmpty () &&
2273                 (!m_updaters_enabled || !m_updaters.first ())) {
2274             killTimer (m_repaint_timer);
2275             m_repaint_timer = 0;
2276         }
2277     } else if (e->timerId () == m_restore_fullscreen_timer) {
2278         xcb_connection_t* connection = QX11Info::connection();
2279         xcb_get_window_attributes_cookie_t cookie = xcb_get_window_attributes(connection, winId());
2280         xcb_get_window_attributes_reply_t* attrs = xcb_get_window_attributes_reply(connection, cookie, nullptr);
2281         if (attrs->map_state == XCB_MAP_STATE_UNMAPPED) {
2282             m_view->dockArea ()->setCentralWidget (this);
2283             killTimer(m_restore_fullscreen_timer);
2284             m_restore_fullscreen_timer = 0;
2285         }
2286         free(attrs);
2287     } else {
2288         qCCritical(LOG_KMPLAYER_COMMON) << "unknown timer " << e->timerId () << " " << m_repaint_timer << endl;
2289         killTimer (e->timerId ());
2290     }
2291 }
2292 
2293 void ViewArea::closeEvent (QCloseEvent * e) {
2294     //qCDebug(LOG_KMPLAYER_COMMON) << "closeEvent";
2295     if (m_fullscreen) {
2296         m_view->fullScreen();
2297         if (!m_view->topLevelWidget ()->isVisible ())
2298             m_view->topLevelWidget ()->setVisible (true);
2299         e->ignore ();
2300     } else
2301         QWidget::closeEvent (e);
2302 }
2303 
2304 IViewer *ViewArea::createVideoWidget () {
2305     VideoOutput *viewer = new VideoOutput (this, m_view);
2306     video_widgets.push_back (viewer);
2307     viewer->setGeometry (IRect (-60, -60, 50, 50));
2308     viewer->setVisible (true);
2309     m_view->controlPanel ()->raise ();
2310     return viewer;
2311 }
2312 
2313 void ViewArea::destroyVideoWidget (IViewer *widget) {
2314     int i = video_widgets.indexOf(widget);
2315     if (i >= 0) {
2316         IViewer *viewer = widget;
2317         delete viewer;
2318         video_widgets.removeAt(i);
2319     } else {
2320         qCWarning(LOG_KMPLAYER_COMMON) << "destroyVideoWidget widget not found" << endl;
2321     }
2322 }
2323 
2324 void ViewArea::setVideoWidgetVisible (bool show) {
2325     const VideoWidgetList::iterator e = video_widgets.end ();
2326     for (VideoWidgetList::iterator it = video_widgets.begin (); it != e; ++it)
2327         static_cast <VideoOutput *> (*it)->setVisible (show);
2328 }
2329 
2330 static void setXSelectInput(WId wid, uint32_t mask) {
2331     xcb_connection_t* connection = QX11Info::connection();
2332     const uint32_t values[] = { mask };
2333     xcb_change_window_attributes(connection, wid, XCB_CW_EVENT_MASK, values);
2334     xcb_query_tree_cookie_t biscuit = xcb_query_tree(connection, wid);
2335     xcb_query_tree_reply_t *reply = xcb_query_tree_reply(connection, biscuit, nullptr);
2336     if (reply) {
2337         xcb_window_t *chlds = xcb_query_tree_children(reply);
2338         for (int i = 0; i < xcb_query_tree_children_length(reply); i++)
2339             setXSelectInput(chlds[i], mask);
2340         free(reply);
2341     } else {
2342         qCDebug(LOG_KMPLAYER_COMMON) << "failed to get x children";
2343     }
2344 }
2345 
2346 bool ViewArea::nativeEventFilter(const QByteArray& eventType, void * message, long *result) {
2347     Q_UNUSED(result)
2348 
2349     if (eventType != "xcb_generic_event_t")
2350         return false;
2351 
2352     xcb_generic_event_t* event = (xcb_generic_event_t*)message;
2353     switch (event->response_type & ~0x80) {
2354     case XCB_UNMAP_NOTIFY: {
2355         xcb_unmap_notify_event_t* ev = (xcb_unmap_notify_event_t*)event;
2356         if (ev->event != ev->window) {
2357             const VideoWidgetList::iterator e = video_widgets.end ();
2358             for (VideoWidgetList::iterator i=video_widgets.begin(); i != e; ++i) {
2359                 if (ev->event == (*i)->ownHandle()) {
2360                     (*i)->embedded(0);
2361                     break;
2362                 }
2363             }
2364         }
2365         break;
2366     }
2367     case XCB_MAP_NOTIFY: {
2368         xcb_map_notify_event_t* ev = (xcb_map_notify_event_t*)event;
2369         if (!ev->override_redirect && ev->event != ev->window) {
2370             xcb_connection_t* connection = QX11Info::connection();
2371             const VideoWidgetList::iterator e = video_widgets.end ();
2372             for (VideoWidgetList::iterator i=video_widgets.begin(); i != e; ++i) {
2373                 if (ev->event == (*i)->ownHandle()) {
2374                     (*i)->embedded(ev->window);
2375                     return false;
2376                 }
2377                 xcb_window_t p = ev->event;
2378                 xcb_window_t w = ev->window;
2379                 xcb_window_t v = (*i)->clientHandle ();
2380                 xcb_window_t va = winId ();
2381                 xcb_window_t root = 0;
2382                 while (p != v) {
2383                     xcb_query_tree_cookie_t cookie = xcb_query_tree(connection, w);
2384                     xcb_query_tree_reply_t *reply = xcb_query_tree_reply(connection, cookie, nullptr);
2385                     if (reply) {
2386                         p = reply->parent;
2387                         root = reply->root;
2388                         free(reply);
2389                     } else {
2390                         qCDebug(LOG_KMPLAYER_COMMON) << "failed to get x parent";
2391                         break;
2392                     }
2393                     if (p == va || p == v || p == root)
2394                         break;
2395                     w = p;
2396                 }
2397                 if (p == v) {
2398                     setXSelectInput (ev->window,
2399                             static_cast <VideoOutput *>(*i)->inputMask ());
2400                     break;
2401                 }
2402             }
2403         }
2404         break;
2405     }
2406     case XCB_MOTION_NOTIFY: {
2407         xcb_motion_notify_event_t* ev = (xcb_motion_notify_event_t*)event;
2408         if (m_view->controlPanelMode () == View::CP_AutoHide) {
2409             const VideoWidgetList::iterator e = video_widgets.end ();
2410             for (VideoWidgetList::iterator i=video_widgets.begin(); i != e; ++i) {
2411                 QPoint p = mapToGlobal (QPoint (0, 0));
2412                 int x = ev->root_x - p.x ();
2413                 int y = ev->root_y - p.y ();
2414                 m_view->mouseMoved(x / devicePixelRatioF(), y / devicePixelRatioF());
2415                 int devicew = (int)(width() * devicePixelRatioF());
2416                 int deviceh = (int)(height() * devicePixelRatioF());
2417                 if (x > 0 && x < devicew && y > 0 && y < deviceh)
2418                     mouseMoved ();
2419             }
2420         }
2421         break;
2422     }
2423     case XCB_KEY_PRESS: {
2424         xcb_key_press_event_t* ev = (xcb_key_press_event_t*)event;
2425         const VideoWidgetList::iterator e = video_widgets.end ();
2426         for (VideoWidgetList::iterator i=video_widgets.begin(); i != e; ++i)
2427             if ((*i)->clientHandle () == ev->event &&
2428                     static_cast <VideoOutput *>(*i)->inputMask() & XCB_EVENT_MASK_KEY_PRESS) {
2429                 if (ev->detail == 41 /*FIXME 'f'*/)
2430                     m_view->fullScreen ();
2431                 break;
2432             }
2433         break;
2434     }
2435     default:
2436         break;
2437     }
2438     return false;
2439 }
2440 
2441 //----------------------------------------------------------------------
2442 
2443 VideoOutput::VideoOutput (QWidget *parent, View * view)
2444   : QX11EmbedContainer (parent),
2445     m_plain_window(0), m_client_window(0), resized_timer(0),
2446     m_bgcolor (0), m_aspect (0.0),
2447     m_view (view)
2448 {
2449     setAcceptDrops (true);
2450     connect (view->viewArea (), &ViewArea::fullScreenChanged,
2451              this, &VideoOutput::fullScreenChanged);
2452     qCDebug(LOG_KMPLAYER_COMMON) << "VideoOutput::VideoOutput" << endl;
2453     setMonitoring (MonitorAll);
2454     setAttribute (Qt::WA_NoSystemBackground, true);
2455 
2456     xcb_connection_t* connection = QX11Info::connection();
2457     xcb_get_window_attributes_cookie_t cookie = xcb_get_window_attributes(connection, winId());
2458     xcb_get_window_attributes_reply_t* attrs = xcb_get_window_attributes_reply(connection, cookie, nullptr);
2459     if (!(attrs->your_event_mask & XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY))
2460         setXSelectInput(winId(), attrs->your_event_mask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY);
2461     free(attrs);
2462     //setProtocol (QXEmbed::XPLAIN);
2463 }
2464 
2465 VideoOutput::~VideoOutput () {
2466     qCDebug(LOG_KMPLAYER_COMMON) << "VideoOutput::~VideoOutput" << endl;
2467     if (m_plain_window) {
2468         xcb_connection_t* connection = QX11Info::connection();
2469         xcb_destroy_window(connection, m_plain_window);
2470         xcb_flush(connection);
2471         m_plain_window = 0;
2472     }
2473 }
2474 
2475 void VideoOutput::useIndirectWidget (bool inderect) {
2476     qCDebug(LOG_KMPLAYER_COMMON) << "setIntermediateWindow " << !!m_plain_window << "->" << inderect;
2477     if (!clientWinId () || !!m_plain_window != inderect) {
2478         xcb_connection_t* connection = QX11Info::connection();
2479         if (inderect) {
2480             if (!m_plain_window) {
2481                 xcb_screen_t* scr = m_view->viewArea()->d->screen_of_display(connection, QX11Info::appScreen());
2482                 m_plain_window = xcb_generate_id(connection);
2483                 uint32_t values[] = { scr->black_pixel, static_cast<uint32_t>(m_input_mask) };
2484                 int devicew = (int)(width() * devicePixelRatioF());
2485                 int deviceh = (int)(height() * devicePixelRatioF());
2486                 xcb_create_window(connection,
2487                         XCB_COPY_FROM_PARENT, m_plain_window, winId(),
2488                         0, 0, devicew, deviceh,
2489                         1, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT,
2490                         XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, values);
2491                 xcb_map_window(connection, m_plain_window);
2492                 xcb_flush(connection);
2493                 //XSync (QX11Info::display (), false);
2494                 //embedClient (m_plain_window);
2495             }
2496             //XClearWindow (QX11Info::display(), m_plain_window);
2497         } else {
2498             if (m_plain_window) {
2499                 xcb_unmap_window(connection, m_plain_window);
2500                 discardClient ();
2501                 xcb_destroy_window(connection, m_plain_window);
2502                 xcb_flush(connection);
2503                 m_plain_window = 0;
2504                 //XSync (QX11Info::display (), false);
2505             }
2506         }
2507     }
2508 }
2509 
2510 void VideoOutput::embedded(WindowId handle) {
2511     qCDebug(LOG_KMPLAYER_COMMON) << "windowChanged " << (int)clientWinId ();
2512     m_client_window = handle;
2513     if (clientWinId () && !resized_timer)
2514          resized_timer = startTimer (50);
2515     if (clientWinId())
2516         setXSelectInput (clientWinId (), m_input_mask);
2517 }
2518 
2519 void VideoOutput::resizeEvent (QResizeEvent *) {
2520     if (clientWinId () && !resized_timer)
2521          resized_timer = startTimer (50);
2522 }
2523 
2524 void VideoOutput::timerEvent (QTimerEvent *e) {
2525     if (e->timerId () == resized_timer) {
2526         killTimer (resized_timer);
2527         resized_timer = 0;
2528         if (clientWinId ()) {
2529             xcb_connection_t* connection = QX11Info::connection();
2530             uint32_t devicew = (uint32_t)(width() * devicePixelRatioF());
2531             uint32_t deviceh = (uint32_t)(height() * devicePixelRatioF());
2532             uint32_t values[] = { 0, 0, devicew, deviceh };
2533             xcb_configure_window(connection, clientWinId(),
2534                     XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
2535                     XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
2536                     values);
2537             xcb_flush(connection);
2538         }
2539     }
2540 }
2541 
2542 WindowId VideoOutput::windowHandle () {
2543     //return m_plain_window ? clientWinId () : winId ();
2544     return m_plain_window ? m_plain_window : winId ();
2545 }
2546 
2547 WindowId VideoOutput::ownHandle () {
2548     return winId ();
2549 }
2550 
2551 WindowId VideoOutput::clientHandle () {
2552     return clientWinId ();
2553 }
2554 
2555 void VideoOutput::setGeometry (const IRect &rect) {
2556     int x = (int)(rect.x() / devicePixelRatioF());
2557     int y = (int)(rect.y() / devicePixelRatioF());
2558     int w = (int)(rect.width() / devicePixelRatioF());
2559     int h = (int)(rect.height() / devicePixelRatioF());
2560     if (m_view->keepSizeRatio ()) {
2561         // scale video widget inside region
2562         int hfw = heightForWidth (w);
2563         if (hfw > 0) {
2564             if (hfw > h) {
2565                 int old_w = w;
2566                 w = int ((1.0 * h * w)/(1.0 * hfw));
2567                 x += (old_w - w) / 2;
2568             } else {
2569                 y += (h - hfw) / 2;
2570                 h = hfw;
2571             }
2572         }
2573     }
2574     setGeometry (x, y, w, h);
2575     setVisible (true);
2576 }
2577 
2578 void VideoOutput::setAspect (float a) {
2579     m_aspect = a;
2580     QRect r = geometry ();
2581     int x = (int)(r.x() * devicePixelRatioF());
2582     int y = (int)(r.y() * devicePixelRatioF());
2583     int w = (int)(r.width() * devicePixelRatioF());
2584     int h = (int)(r.height() * devicePixelRatioF());
2585     m_view->viewArea()->scheduleRepaint(IRect(x, y, w, h));
2586 }
2587 
2588 void VideoOutput::map () {
2589     setVisible (true);
2590 }
2591 
2592 void VideoOutput::unmap () {
2593     setVisible (false);
2594 }
2595 
2596 void VideoOutput::setMonitoring (Monitor m) {
2597     m_input_mask =
2598         //KeyPressMask | KeyReleaseMask |
2599         //EnterWindowMask | LeaveWindowMask |
2600         //FocusChangeMask |
2601         XCB_EVENT_MASK_EXPOSURE |
2602         //StructureNotifyMask |
2603         XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
2604     if (m & MonitorMouse)
2605         m_input_mask |= XCB_EVENT_MASK_POINTER_MOTION;
2606     if (m & MonitorKey)
2607         m_input_mask |= XCB_EVENT_MASK_KEY_PRESS;
2608     if (clientWinId ())
2609         setXSelectInput (clientWinId (), m_input_mask);
2610 }
2611 
2612 void VideoOutput::fullScreenChanged () {
2613     if (!(m_input_mask & XCB_EVENT_MASK_KEY_PRESS)) { // FIXME: store monitor when needed
2614         if (m_view->isFullScreen ())
2615             m_input_mask |= XCB_EVENT_MASK_POINTER_MOTION;
2616         else
2617             m_input_mask &= ~XCB_EVENT_MASK_POINTER_MOTION;
2618     }
2619     if (clientWinId ())
2620         setXSelectInput (clientWinId (), m_input_mask);
2621 }
2622 
2623 int VideoOutput::heightForWidth (int w) const {
2624     if (m_aspect <= 0.01)
2625         return 0;
2626     return int (w/m_aspect);
2627 }
2628 
2629 void VideoOutput::dropEvent (QDropEvent * de) {
2630     m_view->dropEvent (de);
2631 }
2632 
2633 void VideoOutput::dragEnterEvent (QDragEnterEvent* dee) {
2634     m_view->dragEnterEvent (dee);
2635 }
2636 
2637 void VideoOutput::contextMenuEvent (QContextMenuEvent * e) {
2638     m_view->controlPanel ()->popupMenu->exec (e->globalPos ());
2639 }
2640 
2641 void VideoOutput::setBackgroundColor (const QColor & c) {
2642     if (m_bgcolor != c.rgb ()) {
2643         m_bgcolor = c.rgb ();
2644         setCurrentBackgroundColor (c);
2645     }
2646 }
2647 
2648 void VideoOutput::resetBackgroundColor () {
2649     setCurrentBackgroundColor (m_bgcolor);
2650 }
2651 
2652 void VideoOutput::setCurrentBackgroundColor (const QColor & c) {
2653     QPalette palette;
2654     palette.setColor (backgroundRole(), c);
2655     setPalette (palette);
2656     if (clientWinId()) {
2657         xcb_connection_t* connection = QX11Info::connection();
2658         const uint32_t values[] = { c.rgb() };
2659         xcb_change_window_attributes(connection, clientWinId(), XCB_CW_BACK_PIXEL, values);
2660         xcb_flush(connection);
2661     }
2662 }
2663 
2664 #include "moc_viewarea.cpp"