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) << "[01;35mwindowChanged[00m " << (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"