1
# -*- coding: utf-8 -*-
6
# Copyright (c) 2005, Edward Hervey <bilboed@bilboed.com>
7
# Copyright (c) 2012, Thibault Saunier <thibault.saunier@collabora.com>
9
# This program is free software; you can redistribute it and/or
10
# modify it under the terms of the GNU Lesser General Public
11
# License as published by the Free Software Foundation; either
12
# version 2.1 of the License, or (at your option) any later version.
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17
# Lesser General Public License for more details.
19
# You should have received a copy of the GNU Lesser General Public
20
# License along with this program; if not, write to the
21
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22
# Boston, MA 02110-1301, USA.
25
UI utilities. This file contain the UI constants, and various functions and
26
classes that help with UI drawing around the application
35
from gettext import ngettext, gettext as _
37
from gi.repository import Clutter
38
from gi.repository import Cogl
39
from gi.repository import GLib
40
from gi.repository import GES
41
from gi.repository import Gdk
42
from gi.repository import Gio
43
from gi.repository import Gst
44
from gi.repository import Gtk
45
from gi.repository.GstPbutils import DiscovererVideoInfo, DiscovererAudioInfo,\
46
DiscovererStreamInfo, DiscovererSubtitleInfo, DiscovererInfo
48
from pitivi.utils.misc import path_from_uri
50
from pitivi.utils.loggable import doLog, ERROR
52
# ---------------------- Constants -------------------------------------------#
55
# UI pixels information constants
58
LAYER_HEIGHT_EXPANDED = 50
59
LAYER_HEIGHT_COLLAPSED = 15
71
PLAYHEAD_COLOR = Clutter.Color.new(200, 0, 0, 255)
73
# Layer creation blocking time in s
74
LAYER_CREATION_BLOCK_TIME = 0.2
77
# Drag'n drop constants
82
# FileSourceFactory (or subclasses)
83
TYPE_PITIVI_FILESOURCE = 26
85
# What objects to these correspond to ???
86
TYPE_PITIVI_EFFECT = 27
87
TYPE_PITIVI_AUDIO_EFFECT = 28
88
TYPE_PITIVI_VIDEO_EFFECT = 29
89
TYPE_PITIVI_AUDIO_TRANSITION = 30
90
TYPE_PITIVI_VIDEO_TRANSITION = 31
91
TYPE_PITIVI_LAYER_CONTROL = 32
93
FILE_TARGET_ENTRY = Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.OTHER_APP, TYPE_TEXT_PLAIN)
94
URI_TARGET_ENTRY = Gtk.TargetEntry.new("text/uri-list", 0, TYPE_URI_LIST)
95
FILESOURCE_TARGET_ENTRY = Gtk.TargetEntry.new("pitivi/file-source", 0, TYPE_PITIVI_FILESOURCE)
96
EFFECT_TARGET_ENTRY = Gtk.TargetEntry.new("pitivi/effect", 0, TYPE_PITIVI_EFFECT)
97
AUDIO_EFFECT_TARGET_ENTRY = Gtk.TargetEntry.new("pitivi/audio-effect", 0, TYPE_PITIVI_AUDIO_EFFECT)
98
VIDEO_EFFECT_TARGET_ENTRY = Gtk.TargetEntry.new("pitivi/video-effect", 0, TYPE_PITIVI_VIDEO_EFFECT)
99
AUDIO_TRANSITION_TARGET_ENTRY = Gtk.TargetEntry.new("pitivi/audio-transition", 0, TYPE_PITIVI_AUDIO_TRANSITION)
100
VIDEO_TRANSITION_TARGET_ENTRY = Gtk.TargetEntry.new("pitivi/video-transition", 0, TYPE_PITIVI_VIDEO_TRANSITION)
101
LAYER_CONTROL_TARGET_ENTRY = Gtk.TargetEntry.new("pitivi/layer-control", 0, TYPE_PITIVI_LAYER_CONTROL)
104
def _get_settings(schema):
105
if schema not in Gio.Settings.list_schemas():
107
return Gio.Settings(schema=schema)
110
def _get_font(font_spec, default):
112
settings = _get_settings("org.gnome.desktop.interface")
114
if font_spec in settings.list_keys():
115
raw_font = settings.get_string(font_spec)
116
face = raw_font.rsplit(" ", 1)[0]
117
return cairo.ToyFontFace(face)
119
NORMAL_FONT = _get_font("font-name", "Cantarell")
120
DOCUMENT_FONT = _get_font("document-font-name", "Sans")
121
MONOSPACE_FONT = _get_font("monospace-font-name", "Monospace")
124
# ---------------------- ARGB color helper-------------------------------------#
125
def pack_color_32(red, green, blue, alpha=0xFFFF):
126
"""Packs the specified 16bit color values in a 32bit RGBA value."""
131
return (red << 24 | green << 16 | blue << 8 | alpha)
134
def pack_color_64(red, green, blue, alpha=0xFFFF):
135
"""Packs the specified 16bit color values in a 64bit RGBA value."""
136
return (red << 48 | green << 32 | blue << 16 | alpha)
139
def unpack_color(value):
140
"""Unpacks the specified RGBA value into four 16bit color values.
143
value: A 32bit or 64bit RGBA value.
145
if not (value >> 32):
146
return unpack_color_32(value)
148
return unpack_color_64(value)
151
def unpack_color_32(value):
152
"""Unpacks the specified 32bit RGBA value into four 16bit color values."""
153
red = (value >> 24) << 8
154
green = ((value >> 16) & 0xFF) << 8
155
blue = ((value >> 8) & 0xFF) << 8
156
alpha = (value & 0xFF) << 8
157
return red, green, blue, alpha
160
def unpack_color_64(value):
161
"""Unpacks the specified 64bit RGBA value into four 16bit color values."""
162
red = (value >> 48) & 0xFFFF
163
green = (value >> 32) & 0xFFFF
164
blue = (value >> 16) & 0xFFFF
165
alpha = value & 0xFFFF
166
return red, green, blue, alpha
169
def unpack_cairo_pattern(value):
170
"""Transforms the specified RGBA value into a SolidPattern object."""
171
red, green, blue, alpha = unpack_color(value)
172
return cairo.SolidPattern(
179
def unpack_cairo_gradient(value):
180
"""Creates a LinearGradient object out of the specified RGBA value."""
181
red, green, blue, alpha = unpack_color(value)
182
gradient = cairo.LinearGradient(0, 0, 0, 50)
183
gradient.add_color_stop_rgba(
189
gradient.add_color_stop_rgba(
191
(red / 65535.0) * 1.5,
192
(green / 65535.0) * 1.5,
193
(blue / 65535.0) * 1.5,
198
def hex_to_rgb(value):
199
return tuple(float(int(value[i:i + 2], 16)) / 255.0 for i in range(0, 6, 2))
202
def create_cogl_color(red, green, blue, alpha):
204
color.init_from_4ub(red, green, blue, alpha)
208
def set_cairo_color(context, color):
209
if type(color) is Clutter.Color:
210
color = (color.red, color.green, color.blue)
212
if type(color) is Gdk.RGBA:
213
cairo_color = (float(color.red), float(color.green), float(color.blue))
214
elif type(color) is tuple:
215
# Cairo's set_source_rgb function expects values from 0.0 to 1.0
216
cairo_color = map(lambda x: max(0, min(1, x / 255.0)), color)
218
raise Exception("Unexpected color parameter: %s, %s" % (type(color), color))
219
context.set_source_rgb(*cairo_color)
222
def beautify_info(info):
224
Formats the specified info for display.
226
@type info: L{DiscovererInfo}
229
DiscovererVideoInfo: 0,
230
DiscovererAudioInfo: 1,
231
DiscovererStreamInfo: 2
234
def stream_sort_key(stream):
236
return ranks[type(stream)]
240
info.get_stream_list().sort(key=stream_sort_key)
241
nice_streams_txts = []
242
for stream in info.get_stream_list():
244
beautified_string = beautify_stream(stream)
245
except NotImplementedError:
246
doLog(ERROR, "Beautify", "None", "Cannot beautify %s", stream)
248
if beautified_string:
249
nice_streams_txts.append(beautified_string)
251
return ("<b>" + path_from_uri(info.get_uri()) + "</b>\n" +
252
"\n".join(nice_streams_txts))
257
Return a human-readable filename (without the path and quoting).
259
@type info: L{GES.Asset} or L{DiscovererInfo}
261
if isinstance(info, GES.Asset):
262
filename = urllib.unquote(os.path.basename(info.get_id()))
263
elif isinstance(info, DiscovererInfo):
264
filename = urllib.unquote(os.path.basename(info.get_uri()))
266
raise Exception("Unsupported argument type: %s" % type(info))
267
return GLib.markup_escape_text(filename)
270
def beautify_stream(stream):
271
if type(stream) is DiscovererAudioInfo:
272
templ = ngettext("<b>Audio:</b> %d channel at %d <i>Hz</i> (%d <i>bits</i>)",
273
"<b>Audio:</b> %d channels at %d <i>Hz</i> (%d <i>bits</i>)",
274
stream.get_channels())
275
templ = templ % (stream.get_channels(), stream.get_sample_rate(),
279
elif type(stream) is DiscovererVideoInfo:
280
par = stream.get_par_num() / stream.get_par_denom()
281
if not stream.is_image():
282
templ = _("<b>Video:</b> %d×%d <i>pixels</i> at %.3f <i>fps</i>")
284
templ = templ % (par * stream.get_width(), stream.get_height(),
285
float(stream.get_framerate_num()) / stream.get_framerate_denom())
286
except ZeroDivisionError:
287
templ = templ % (par * stream.get_width(), stream.get_height(), 0)
289
templ = _("<b>Image:</b> %d×%d <i>pixels</i>")
290
templ = templ % (par * stream.get_width(), stream.get_height())
293
elif type(stream) is DiscovererSubtitleInfo:
294
# Ignore subtitle streams
297
elif type(stream) is DiscovererStreamInfo:
298
caps = stream.get_caps().to_string()
299
if caps in ("application/x-subtitle", "application/x-id3", "text"):
300
# Ignore all audio ID3 tags and subtitle tracks, we don't show them
303
raise NotImplementedError
306
def time_to_string(value):
308
Converts the given time in nanoseconds to a human readable string
312
if value == Gst.CLOCK_TIME_NONE:
313
return "--:--:--.---"
314
ms = value / Gst.MSECOND
321
return "%01d:%02d:%02d.%03d" % (hours, mins, sec, ms)
324
def beautify_length(length):
326
Converts the given time in nanoseconds to a human readable string
328
sec = length / Gst.SECOND
336
parts.append(ngettext("%d hour", "%d hours", hours) % hours)
339
parts.append(ngettext("%d minute", "%d minutes", mins) % mins)
341
if not hours and sec:
342
parts.append(ngettext("%d second", "%d seconds", sec) % sec)
344
return ", ".join(parts)
347
def beautify_time_delta(seconds):
349
Converts the given time in seconds to a human-readable estimate.
351
This is intended for "Unsaved changes" and "Backup file found" dialogs.
354
sec = int(seconds % 60)
356
mins = int(mins % 60)
357
days = int(hours / 24)
358
hours = int(hours % 24)
362
parts.append(ngettext("%d day", "%d days", days) % days)
364
parts.append(ngettext("%d hour", "%d hours", hours) % hours)
366
if days == 0 and mins > 0:
367
parts.append(ngettext("%d minute", "%d minutes", mins) % mins)
369
if hours == 0 and mins < 2 and sec:
370
parts.append(ngettext("%d second", "%d seconds", sec) % sec)
372
return ", ".join(parts)
375
def beautify_ETA(length):
377
Converts the given time in nanoseconds to a fuzzy estimate,
378
intended for progress ETAs, not to indicate a clip's duration.
380
sec = length / Gst.SECOND
383
hours = int(mins / 60)
384
mins = int(mins % 60)
388
parts.append(ngettext("%d hour", "%d hours", hours) % hours)
391
parts.append(ngettext("%d minute", "%d minutes", mins) % mins)
393
if hours == 0 and mins < 2 and sec:
394
parts.append(ngettext("%d second", "%d seconds", sec) % sec)
395
return ", ".join(parts)
398
#--------------------- Gtk widget helpers ------------------------------------#
399
def model(columns, data):
400
ret = Gtk.ListStore(*columns)
406
def set_combo_value(combo, value, default_index=-1):
407
model = combo.props.model
408
for i, row in enumerate(model):
412
combo.set_active(default_index)
415
def get_combo_value(combo):
416
active = combo.get_active()
417
return combo.props.model[active][1]
420
def get_value_from_model(model, key):
422
For a given key, search a gtk ListStore and return the value as a string.
424
If not found and the key is a gst fraction, return a beautified form.
429
if isinstance(key, Gst.Fraction):
430
return "%.3f" % decimal.Decimal(float(key.num) / key.denom)
434
def alter_style_class(style_class, target_widget, css_style):
435
css_provider = Gtk.CssProvider()
436
toolbar_css = "%s { %s }" % (style_class, css_style)
437
css_provider.load_from_data(toolbar_css.encode('UTF-8'))
438
style_context = target_widget.get_style_context()
439
style_context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
442
#------------------------ encoding datas ----------------------------------------#
443
# FIXME This should into a special file
444
frame_rates = model((str, object), (
445
# Translators: fps is for frames per second
446
(_("%d fps") % 12, Gst.Fraction(12.0, 1.0)),
447
(_("%d fps") % 15, Gst.Fraction(15.0, 1.0)),
448
(_("%d fps") % 20, Gst.Fraction(20.0, 1.0)),
449
(_("%.3f fps") % 23.976, Gst.Fraction(24000.0, 1001.0)),
450
(_("%d fps") % 24, Gst.Fraction(24.0, 1.0)),
451
(_("%d fps") % 25, Gst.Fraction(25.0, 1.0)),
452
(_("%.2f fps") % 29.97, Gst.Fraction(30000.0, 1001.0)),
453
(_("%d fps") % 30, Gst.Fraction(30.0, 1.0)),
454
(_("%d fps") % 50, Gst.Fraction(50.0, 1.0)),
455
(_("%.2f fps") % 59.94, Gst.Fraction(60000.0, 1001.0)),
456
(_("%d fps") % 60, Gst.Fraction(60.0, 1.0)),
457
(_("%d fps") % 120, Gst.Fraction(120.0, 1.0)),
460
audio_rates = model((str, int), (
461
(_("%d kHz") % 8, 8000),
462
(_("%d kHz") % 11, 11025),
463
(_("%d kHz") % 22, 22050),
464
(_("%.1f kHz") % 44.1, 44100),
465
(_("%d kHz") % 48, 48000),
466
(_("%d kHz") % 96, 96000)))
468
audio_channels = model((str, int), (
469
(_("6 Channels (5.1)"), 6),
470
(_("4 Channels (4.0)"), 4),
474
# FIXME: are we sure the following tables correct?
476
pixel_aspect_ratios = model((str, object), (
477
(_("Square"), Gst.Fraction(1, 1)),
478
(_("480p"), Gst.Fraction(10, 11)),
479
(_("480i"), Gst.Fraction(8, 9)),
480
(_("480p Wide"), Gst.Fraction(40, 33)),
481
(_("480i Wide"), Gst.Fraction(32, 27)),
482
(_("576p"), Gst.Fraction(12, 11)),
483
(_("576i"), Gst.Fraction(16, 15)),
484
(_("576p Wide"), Gst.Fraction(16, 11)),
485
(_("576i Wide"), Gst.Fraction(64, 45)),
488
display_aspect_ratios = model((str, object), (
489
(_("Standard (4:3)"), Gst.Fraction(4, 3)),
490
(_("DV (15:11)"), Gst.Fraction(15, 11)),
491
(_("DV Widescreen (16:9)"), Gst.Fraction(16, 9)),
492
(_("Cinema (1.37)"), Gst.Fraction(11, 8)),
493
(_("Cinema (1.66)"), Gst.Fraction(166, 100)),
494
(_("Cinema (1.85)"), Gst.Fraction(185, 100)),
495
(_("Anamorphic (2.35)"), Gst.Fraction(235, 100)),
496
(_("Anamorphic (2.39)"), Gst.Fraction(239, 100)),
497
(_("Anamorphic (2.4)"), Gst.Fraction(24, 10)),