1/*
2 * Copyright 2006-2010 Stephan A��mus <superstippi@gmx.de>
3 * All rights reserved. Distributed under the terms of the MIT license.
4 */
5
6
7#include "VideoView.h"
8
9#include <stdio.h>
10
11#include <Application.h>
12#include <Bitmap.h>
13#include <Region.h>
14#include <Screen.h>
15#include <WindowScreen.h>
16
17#include "Settings.h"
18#include "SubtitleBitmap.h"
19
20
21enum {
22	MSG_INVALIDATE = 'ivdt'
23};
24
25
26VideoView::VideoView(BRect frame, const char* name, uint32 resizeMask)
27	:
28	BView(frame, name, resizeMask, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE
29		| B_PULSE_NEEDED),
30	fVideoFrame(Bounds()),
31	fOverlayMode(false),
32	fIsPlaying(false),
33	fIsFullscreen(false),
34	fFullscreenControlsVisible(false),
35	fFirstPulseAfterFullscreen(false),
36	fSendHideCounter(0),
37	fLastMouseMove(system_time()),
38
39	fSubtitleBitmap(new SubtitleBitmap),
40	fSubtitleFrame(),
41	fSubtitleMaxButtom(Bounds().bottom),
42	fHasSubtitle(false),
43	fSubtitleChanged(false),
44
45	fGlobalSettingsListener(this)
46{
47	SetViewColor(B_TRANSPARENT_COLOR);
48	SetHighColor(0, 0, 0);
49
50	// create some hopefully sensible default overlay restrictions
51	// they will be adjusted when overlays are actually used
52	fOverlayRestrictions.min_width_scale = 0.25;
53	fOverlayRestrictions.max_width_scale = 8.0;
54	fOverlayRestrictions.min_height_scale = 0.25;
55	fOverlayRestrictions.max_height_scale = 8.0;
56
57	Settings::Default()->AddListener(&fGlobalSettingsListener);
58	_AdoptGlobalSettings();
59
60//SetSubTitle("<b><i>This</i></b> is a <font color=\"#00ff00\">test</font>!"
61//	"\nWith a <i>short</i> line and a <b>long</b> line.");
62}
63
64
65VideoView::~VideoView()
66{
67	Settings::Default()->RemoveListener(&fGlobalSettingsListener);
68	delete fSubtitleBitmap;
69}
70
71
72void
73VideoView::Draw(BRect updateRect)
74{
75	BRegion outSideVideoRegion(updateRect);
76
77	if (LockBitmap()) {
78		if (const BBitmap* bitmap = GetBitmap()) {
79			outSideVideoRegion.Exclude(fVideoFrame);
80			if (!fOverlayMode)
81				_DrawBitmap(bitmap);
82			else
83				FillRect(fVideoFrame & updateRect, B_SOLID_LOW);
84		}
85		UnlockBitmap();
86	}
87
88	if (outSideVideoRegion.CountRects() > 0)
89		FillRegion(&outSideVideoRegion);
90
91	if (fHasSubtitle)
92		_DrawSubtitle();
93}
94
95
96void
97VideoView::MessageReceived(BMessage* message)
98{
99	switch (message->what) {
100		case MSG_OBJECT_CHANGED:
101			// received from fGlobalSettingsListener
102			// TODO: find out which object, if we ever watch more than
103			// the global settings instance...
104			_AdoptGlobalSettings();
105			break;
106		case MSG_INVALIDATE:
107		{
108			BRect dirty;
109			if (message->FindRect("dirty", &dirty) == B_OK)
110				Invalidate(dirty);
111			break;
112		}
113		default:
114			BView::MessageReceived(message);
115	}
116}
117
118
119/*!
120	Disables the screen saver, and hides the full screen controls.
121*/
122void
123VideoView::Pulse()
124{
125	if (!fIsFullscreen || !fIsPlaying)
126		return;
127
128	bigtime_t now = system_time();
129	if (now - fLastMouseMove > 1500000) {
130		fLastMouseMove = now;
131		BPoint where;
132		uint32 buttons;
133		GetMouse(&where, &buttons, false);
134		if (buttons == 0) {
135			// Hide the full screen controls (and the mouse pointer)
136			// after a while
137			if (fFullscreenControlsVisible || fFirstPulseAfterFullscreen) {
138				if (fSendHideCounter == 0 || fSendHideCounter == 3) {
139					// Send after 1.5s and after 4.5s
140					BMessage message(M_HIDE_FULL_SCREEN_CONTROLS);
141					message.AddPoint("where", where);
142					if (fSendHideCounter > 0)
143						message.AddBool("force", true);
144					Window()->PostMessage(&message, Window());
145				}
146				fSendHideCounter++;
147				fFirstPulseAfterFullscreen = false;
148			}
149
150			// Take care of disabling the screen saver
151			ConvertToScreen(&where);
152			set_mouse_position((int32)where.x, (int32)where.y);
153		}
154	}
155}
156
157
158void
159VideoView::MouseMoved(BPoint where, uint32 transit,
160	const BMessage* dragMessage)
161{
162	fLastMouseMove = system_time();
163}
164
165
166// #pragma mark -
167
168
169void
170VideoView::SetBitmap(const BBitmap* bitmap)
171{
172	VideoTarget::SetBitmap(bitmap);
173	// Attention: Don't lock the window, if the bitmap is NULL. Otherwise
174	// we're going to deadlock when the window tells the node manager to
175	// stop the nodes (Window -> NodeManager -> VideoConsumer -> VideoView
176	// -> Window).
177	if (!bitmap || LockLooperWithTimeout(10000) != B_OK)
178		return;
179
180	if (LockBitmap()) {
181		if (fOverlayMode
182			|| (bitmap->Flags() & B_BITMAP_WILL_OVERLAY) != 0) {
183			if (!fOverlayMode) {
184				// init overlay
185				rgb_color key;
186				status_t ret = SetViewOverlay(bitmap, bitmap->Bounds(),
187					fVideoFrame, &key, B_FOLLOW_ALL,
188					B_OVERLAY_FILTER_HORIZONTAL | B_OVERLAY_FILTER_VERTICAL);
189				if (ret == B_OK) {
190					fOverlayKeyColor = key;
191					SetLowColor(key);
192					snooze(20000);
193					FillRect(fVideoFrame, B_SOLID_LOW);
194					Sync();
195					// use overlay from here on
196					_SetOverlayMode(true);
197
198					// update restrictions
199					overlay_restrictions restrictions;
200					if (bitmap->GetOverlayRestrictions(&restrictions) == B_OK)
201						fOverlayRestrictions = restrictions;
202				} else {
203					// try again next time
204					// synchronous draw
205					FillRect(fVideoFrame);
206					Sync();
207				}
208			} else {
209				// transfer overlay channel
210				rgb_color key;
211				SetViewOverlay(bitmap, bitmap->Bounds(), fVideoFrame,
212					&key, B_FOLLOW_ALL, B_OVERLAY_FILTER_HORIZONTAL
213						| B_OVERLAY_FILTER_VERTICAL
214						| B_OVERLAY_TRANSFER_CHANNEL);
215			}
216		} else if (fOverlayMode
217			&& (bitmap->Flags() & B_BITMAP_WILL_OVERLAY) == 0) {
218			_SetOverlayMode(false);
219			ClearViewOverlay();
220			SetViewColor(B_TRANSPARENT_COLOR);
221		}
222		if (!fOverlayMode) {
223			if (fSubtitleChanged) {
224				_LayoutSubtitle();
225				Invalidate(fVideoFrame | fSubtitleFrame);
226			} else if (fHasSubtitle
227				&& fVideoFrame.Intersects(fSubtitleFrame)) {
228				Invalidate(fVideoFrame);
229			} else
230				_DrawBitmap(bitmap);
231		}
232
233		UnlockBitmap();
234	}
235	UnlockLooper();
236}
237
238
239void
240VideoView::GetOverlayScaleLimits(float* minScale, float* maxScale) const
241{
242	*minScale = max_c(fOverlayRestrictions.min_width_scale,
243		fOverlayRestrictions.min_height_scale);
244	*maxScale = max_c(fOverlayRestrictions.max_width_scale,
245		fOverlayRestrictions.max_height_scale);
246}
247
248
249void
250VideoView::OverlayScreenshotPrepare()
251{
252	// TODO: Do nothing if the current bitmap is in RGB color space
253	// and no overlay. Otherwise, convert current bitmap to RGB color
254	// space an draw it in place of the normal display.
255}
256
257
258void
259VideoView::OverlayScreenshotCleanup()
260{
261	// TODO: Do nothing if the current bitmap is in RGB color space
262	// and no overlay. Otherwise clean view area with overlay color.
263}
264
265
266bool
267VideoView::UseOverlays() const
268{
269	return fUseOverlays;
270}
271
272
273bool
274VideoView::IsOverlayActive()
275{
276	bool active = false;
277	if (LockBitmap()) {
278		active = fOverlayMode;
279		UnlockBitmap();
280	}
281	return active;
282}
283
284
285void
286VideoView::DisableOverlay()
287{
288	if (!fOverlayMode)
289		return;
290
291	FillRect(Bounds());
292	Sync();
293
294	ClearViewOverlay();
295	snooze(20000);
296	Sync();
297	_SetOverlayMode(false);
298}
299
300
301void
302VideoView::SetPlaying(bool playing)
303{
304	fIsPlaying = playing;
305}
306
307
308void
309VideoView::SetFullscreen(bool fullScreen)
310{
311	fIsFullscreen = fullScreen;
312	fSendHideCounter = 0;
313	fFirstPulseAfterFullscreen = true;
314}
315
316
317void
318VideoView::SetFullscreenControlsVisible(bool visible)
319{
320	fFullscreenControlsVisible = visible;
321	fSendHideCounter = 0;
322}
323
324
325void
326VideoView::SetVideoFrame(const BRect& frame)
327{
328	if (fVideoFrame == frame)
329		return;
330
331	BRegion invalid(fVideoFrame | frame);
332	invalid.Exclude(frame);
333	Invalidate(&invalid);
334
335	fVideoFrame = frame;
336
337	fSubtitleBitmap->SetVideoBounds(fVideoFrame.OffsetToCopy(B_ORIGIN));
338	_LayoutSubtitle();
339}
340
341
342void
343VideoView::SetSubTitle(const char* text)
344{
345	BRect oldSubtitleFrame = fSubtitleFrame;
346
347	if (text == NULL || text[0] == '\0') {
348		fHasSubtitle = false;
349		fSubtitleChanged = true;
350	} else {
351		fHasSubtitle = true;
352		// If the subtitle frame still needs to be invalidated during
353		// normal playback, make sure we don't unset the fSubtitleChanged
354		// flag. It will be reset after drawing the subtitle once.
355		fSubtitleChanged = fSubtitleBitmap->SetText(text) || fSubtitleChanged;
356		if (fSubtitleChanged)
357			_LayoutSubtitle();
358	}
359
360	if (!fIsPlaying && Window() != NULL) {
361		// If we are playing, the new subtitle will be displayed,
362		// or the old one removed from screen, as soon as the next
363		// frame is shown. Otherwise we need to invalidate manually.
364		// But we are not in the window thread and we shall not lock
365		// it or we may dead-locks.
366		BMessage message(MSG_INVALIDATE);
367		message.AddRect("dirty", oldSubtitleFrame | fSubtitleFrame);
368		Window()->PostMessage(&message);
369	}
370}
371
372
373void
374VideoView::SetSubTitleMaxBottom(float bottom)
375{
376	if (bottom == fSubtitleMaxButtom)
377		return;
378
379	fSubtitleMaxButtom = bottom;
380
381	BRect oldSubtitleFrame = fSubtitleFrame;
382	_LayoutSubtitle();
383	Invalidate(fSubtitleFrame | oldSubtitleFrame);
384}
385
386
387// #pragma mark -
388
389
390void
391VideoView::_DrawBitmap(const BBitmap* bitmap)
392{
393	SetDrawingMode(B_OP_COPY);
394	uint32 options = B_WAIT_FOR_RETRACE;
395	if (fUseBilinearScaling)
396		options |= B_FILTER_BITMAP_BILINEAR;
397
398	DrawBitmapAsync(bitmap, bitmap->Bounds(), fVideoFrame, options);
399}
400
401
402void
403VideoView::_DrawSubtitle()
404{
405	SetDrawingMode(B_OP_ALPHA);
406	SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
407
408	DrawBitmapAsync(fSubtitleBitmap->Bitmap(), fSubtitleFrame.LeftTop());
409
410	// Unless the subtitle frame intersects the video frame, we don't have
411	// to draw the subtitle again.
412	fSubtitleChanged = false;
413}
414
415
416void
417VideoView::_AdoptGlobalSettings()
418{
419	mpSettings settings;
420	Settings::Default()->Get(settings);
421
422	fUseOverlays = settings.useOverlays;
423	fUseBilinearScaling = settings.scaleBilinear;
424
425	switch (settings.subtitleSize) {
426		case mpSettings::SUBTITLE_SIZE_SMALL:
427			fSubtitleBitmap->SetCharsPerLine(45.0);
428			break;
429		case mpSettings::SUBTITLE_SIZE_MEDIUM:
430			fSubtitleBitmap->SetCharsPerLine(36.0);
431			break;
432		case mpSettings::SUBTITLE_SIZE_LARGE:
433			fSubtitleBitmap->SetCharsPerLine(32.0);
434			break;
435	}
436
437	fSubtitlePlacement = settings.subtitlePlacement;
438
439	_LayoutSubtitle();
440	Invalidate();
441}
442
443
444void
445VideoView::_SetOverlayMode(bool overlayMode)
446{
447	fOverlayMode = overlayMode;
448	fSubtitleBitmap->SetOverlayMode(overlayMode);
449}
450
451
452void
453VideoView::_LayoutSubtitle()
454{
455	if (!fHasSubtitle)
456		return;
457
458	const BBitmap* subtitleBitmap = fSubtitleBitmap->Bitmap();
459	if (subtitleBitmap == NULL)
460		return;
461
462	fSubtitleFrame = subtitleBitmap->Bounds();
463
464	BPoint offset;
465	offset.x = (fVideoFrame.left + fVideoFrame.right
466		- fSubtitleFrame.Width()) / 2;
467	switch (fSubtitlePlacement) {
468		default:
469		case mpSettings::SUBTITLE_PLACEMENT_BOTTOM_OF_VIDEO:
470			offset.y = min_c(fSubtitleMaxButtom, fVideoFrame.bottom)
471				- fSubtitleFrame.Height();
472			break;
473		case mpSettings::SUBTITLE_PLACEMENT_BOTTOM_OF_SCREEN:
474		{
475			// Center between video and screen bottom, if there is still
476			// enough room.
477			float centeredOffset = (fVideoFrame.bottom + fSubtitleMaxButtom
478				- fSubtitleFrame.Height()) / 2;
479			float maxOffset = fSubtitleMaxButtom - fSubtitleFrame.Height();
480			offset.y = min_c(centeredOffset, maxOffset);
481			break;
482		}
483	}
484
485	fSubtitleFrame.OffsetTo(offset);
486}
487
488
489