Port preferences window to libadwaita

This commit is contained in:
Rafał Dzięgiel
2021-07-10 08:50:01 +02:00
committed by Rafostar
parent 62fab289b7
commit c8a5277908
12 changed files with 822 additions and 703 deletions

View File

@@ -1,354 +1,475 @@
const { GObject, Gst, Gtk, Pango } = imports.gi;
const { Adw, GObject, Gio, Gst, Gtk } = imports.gi;
const Debug = imports.src.debug;
const Misc = imports.src.misc;
const PrefsBase = imports.src.prefsBase;
const { debug } = Debug;
const { settings } = Misc;
/* PlayFlags are not exported through GI */
Gst.PlayFlags = {
VIDEO: 1,
AUDIO: 2,
TEXT: 4,
VIS: 8,
SOFT_VOLUME: 16,
NATIVE_AUDIO: 32,
NATIVE_VIDEO: 64,
DOWNLOAD: 128,
BUFFERING: 256,
DEINTERLACE: 512,
SOFT_COLORBALANCE: 1024,
FORCE_FILTERS: 2048,
FORCE_SW_DECODERS: 4096,
VIDEO: 1,
AUDIO: 2,
TEXT: 4,
VIS: 8,
SOFT_VOLUME: 16,
NATIVE_AUDIO: 32,
NATIVE_VIDEO: 64,
DOWNLOAD: 128,
BUFFERING: 256,
DEINTERLACE: 512,
SOFT_COLORBALANCE: 1024,
FORCE_FILTERS: 2048,
FORCE_SW_DECODERS: 4096,
};
var GeneralPage = GObject.registerClass(
class ClapperGeneralPage extends PrefsBase.Grid
const widgetOpts = {
halign: Gtk.Align.CENTER,
valign: Gtk.Align.CENTER,
};
function getCommonProps()
{
_init()
return {
'schema-name': GObject.ParamSpec.string(
'schema-name',
'GSchema setting name',
'Name of the setting to bind',
GObject.ParamFlags.WRITABLE,
null
),
};
}
const flags = Gio.SettingsBindFlags.DEFAULT;
let PrefsActionRow = GObject.registerClass({
GTypeName: 'ClapperPrefsActionRow',
Properties: getCommonProps(),
},
class ClapperPrefsActionRow extends Adw.ActionRow
{
_init(widget)
{
super._init();
this.addTitle('Startup');
this.addCheckButton('Auto enter fullscreen', 'fullscreen-auto');
this._schemaName = null;
this._bindProp = null;
this.addTitle('Volume');
const comboBox = this.addComboBoxText('Initial value', [
['restore', "Restore"],
['custom', "Custom"],
], 'volume-initial');
const spinButton = this.addSpinButton('Value (percentage)', 0, 200, 'volume-value');
this._onVolumeInitialChanged(spinButton, comboBox);
comboBox.connect('changed', this._onVolumeInitialChanged.bind(this, spinButton));
this.addTitle('Finish');
this.addCheckButton('Keep showing last frame', 'keep-last-frame');
this.addCheckButton('Close after playback', 'close-auto');
this.add_suffix(widget);
this.set_activatable_widget(widget);
}
_onVolumeInitialChanged(spinButton, comboBox)
set schema_name(value)
{
const value = comboBox.get_active_id();
spinButton.set_visible(value === 'custom');
}
});
var BehaviourPage = GObject.registerClass(
class ClapperBehaviourPage extends PrefsBase.Grid
{
_init()
{
super._init();
this.addTitle('Seeking');
this.addComboBoxText('Mode', [
['normal', "Normal"],
['accurate', "Accurate"],
['fast', "Fast"],
], 'seeking-mode');
this.addComboBoxText('Unit', [
['second', "Second"],
['minute', "Minute"],
['percentage', "Percentage"],
], 'seeking-unit');
this.addSpinButton('Value', 1, 99, 'seeking-value');
this.addTitle('Resume');
this.addCheckButton('Ask to resume last unfinished video', 'resume-enabled');
this.addTitle('Floating Mode');
this.addCheckButton('Show on all workspaces', 'floating-stick');
}
});
var AudioPage = GObject.registerClass(
class ClapperAudioPage extends PrefsBase.Grid
{
_init()
{
super._init();
this.addTitle('Synchronization');
this.addSpinButton('Offset (milliseconds)', -1000, 1000, 'audio-offset', 25);
this.addTitle('Processing');
this.addPlayFlagCheckButton('Only use native audio formats', Gst.PlayFlags.NATIVE_AUDIO);
}
});
var SubtitlesPage = GObject.registerClass(
class ClapperSubtitlesPage extends PrefsBase.Grid
{
_init()
{
super._init();
/* FIXME: This should be moved to subtitles popup and displayed only when
external subtitles were added for easier customization per video. */
//this.addTitle('Synchronization');
//this.addSpinButton('Offset (milliseconds)', -5000, 5000, 'subtitle-offset', 25);
this.addTitle('External Subtitles');
this.addFontButton('Default font', 'subtitle-font');
}
});
var NetworkPage = GObject.registerClass(
class ClapperNetworkPage extends PrefsBase.Grid
{
_init()
{
super._init();
this.addTitle('Client');
this.addPlayFlagCheckButton('Progressive download buffering', Gst.PlayFlags.DOWNLOAD);
this.addTitle('Server');
const webServer = this.addCheckButton('Control player remotely', 'webserver-enabled');
const serverPort = this.addSpinButton('Listening port', 1024, 65535, 'webserver-port');
webServer.bind_property('active', serverPort, 'visible', GObject.BindingFlags.SYNC_CREATE);
const webApp = this.addCheckButton('Start built-in web application', 'webapp-enabled');
webServer.bind_property('active', webApp, 'visible', GObject.BindingFlags.SYNC_CREATE);
const webAppPort = this.addSpinButton('Web application port', 1024, 65535, 'webapp-port');
webServer.bind_property('active', webAppPort, 'visible', GObject.BindingFlags.SYNC_CREATE);
}
});
var YouTubePage = GObject.registerClass(
class ClapperYouTubePage extends PrefsBase.Grid
{
_init()
{
super._init();
this.addTitle('YouTube');
this.addCheckButton('Prefer adaptive streaming', 'yt-adaptive-enabled');
this.addComboBoxText('Max quality', [
['normal', "Normal"],
['hfr', "HFR"],
], 'yt-quality-type');
}
});
var GStreamerPage = GObject.registerClass(
class ClapperGStreamerPage extends PrefsBase.Grid
{
_init()
{
super._init();
this.addTitle('Plugin Ranking');
const listStore = new Gtk.ListStore();
listStore.set_column_types([
GObject.TYPE_BOOLEAN,
GObject.TYPE_STRING,
GObject.TYPE_STRING,
]);
const treeView = new Gtk.TreeView({
hexpand: true,
vexpand: true,
enable_search: false,
model: listStore,
});
const treeSelection = treeView.get_selection();
const apply = new Gtk.TreeViewColumn({
title: "Apply",
});
const name = new Gtk.TreeViewColumn({
title: "Plugin",
expand: true,
});
const rank = new Gtk.TreeViewColumn({
title: "Rank",
min_width: 90,
});
const applyCell = new Gtk.CellRendererToggle();
const nameCell = new Gtk.CellRendererText({
editable: true,
placeholder_text: "Insert plugin name",
});
const rankCell = new Gtk.CellRendererText({
editable: true,
weight: Pango.Weight.BOLD,
placeholder_text: "Insert plugin rank",
});
apply.pack_start(applyCell, true);
name.pack_start(nameCell, true);
rank.pack_start(rankCell, true);
apply.add_attribute(applyCell, 'active', 0);
name.add_attribute(nameCell, 'text', 1);
rank.add_attribute(rankCell, 'text', 2);
treeView.insert_column(apply, 0);
treeView.insert_column(name, 1);
treeView.insert_column(rank, 2);
const frame = new Gtk.Frame({
child: treeView
});
this.addToGrid(frame);
const addButton = new Gtk.Button({
icon_name: 'list-add-symbolic',
halign: Gtk.Align.END,
});
const removeButton = new Gtk.Button({
icon_name: 'list-remove-symbolic',
sensitive: false,
halign: Gtk.Align.END,
});
const label = new Gtk.Label({
label: 'Changes require player restart',
halign: Gtk.Align.START,
hexpand: true,
ellipsize: Pango.EllipsizeMode.END,
});
const box = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
spacing: 6,
hexpand: true,
});
box.append(label);
box.append(removeButton);
box.append(addButton);
this.addToGrid(box);
applyCell.connect('toggled', this._onApplyCellEdited.bind(this));
nameCell.connect('edited', this._onNameCellEdited.bind(this));
rankCell.connect('edited', this._onRankCellEdited.bind(this));
addButton.connect('clicked', this._onAddButtonClicked.bind(this, listStore));
removeButton.connect('clicked', this._onRemoveButtonClicked.bind(this, listStore));
treeSelection.connect('changed', this._onTreeSelectionChanged.bind(this, removeButton));
this.settingsChangedSignal = settings.connect(
'changed::plugin-ranking', this.refreshListStore.bind(this, listStore)
);
this.refreshListStore(listStore);
this._schemaName = value;
}
refreshListStore(listStore)
vfunc_realize()
{
const data = JSON.parse(settings.get_string('plugin-ranking'));
listStore.clear();
super.vfunc_realize();
for(let plugin of data) {
listStore.set(
listStore.append(),
[0, 1, 2], [
plugin.apply || false,
plugin.name || '',
plugin.rank || 0
]
if(this._schemaName && this._bindProp) {
settings.bind(this._schemaName,
this.activatable_widget, this._bindProp, flags
);
}
}
updatePlugin(index, prop, value)
{
const data = JSON.parse(settings.get_string('plugin-ranking'));
data[index][prop] = value;
settings.set_string('plugin-ranking', JSON.stringify(data));
}
_onTreeSelectionChanged(removeButton, treeSelection)
{
const [isSelected, model, iter] = treeSelection.get_selected();
this.activeIndex = -1;
if(isSelected) {
this.activeIndex = Number(model.get_string_from_iter(iter));
}
removeButton.set_sensitive(this.activeIndex >= 0);
}
_onAddButtonClicked(listStore, button)
{
const data = JSON.parse(settings.get_string('plugin-ranking'));
data.push({
apply: false,
name: '',
rank: 0,
});
settings.set_string('plugin-ranking', JSON.stringify(data));
}
_onRemoveButtonClicked(listStore, button)
{
if(this.activeIndex < 0)
return;
const data = JSON.parse(settings.get_string('plugin-ranking'));
data.splice(this.activeIndex, 1);
settings.set_string('plugin-ranking', JSON.stringify(data));
}
_onApplyCellEdited(cell, path)
{
const newState = !cell.active;
this.updatePlugin(path, 'apply', newState);
}
_onNameCellEdited(cell, path, newText)
{
newText = newText.trim();
this.updatePlugin(path, 'name', newText);
}
_onRankCellEdited(cell, path, newText)
{
newText = newText.trim();
if(isNaN(newText))
newText = 0;
this.updatePlugin(path, 'rank', Number(newText));
}
_onClose()
{
super._onClose('gstreamer');
settings.disconnect(this.settingsChangedSignal);
this.settingsChangedSignal = null;
this._schemaName = null;
}
});
var TweaksPage = GObject.registerClass(
class ClapperTweaksPage extends PrefsBase.Grid
let PrefsSubpageRow = GObject.registerClass({
GTypeName: 'ClapperPrefsSubpageRow',
Properties: getCommonProps(),
},
class ClapperPrefsSubpageRow extends Adw.ActionRow
{
_init(widget)
{
super._init({
activatable: true,
});
this._prefsSubpage = null;
const icon = new Gtk.Image({
icon_name: 'go-next-symbolic',
});
this.add_suffix(icon);
}
vfunc_activate()
{
super.vfunc_activate();
if(!this._prefsSubpage)
this._prefsSubpage = this._createSubpage();
const prefs = this.get_ancestor(PrefsWindow);
prefs.present_subpage(this._prefsSubpage);
}
_createSubpage()
{
/* For override */
return null;
}
});
GObject.registerClass({
GTypeName: 'ClapperPrefsSwitch',
},
class ClapperPrefsSwitch extends PrefsActionRow
{
_init()
{
super._init(new Gtk.Switch(widgetOpts));
this._bindProp = 'active';
}
});
GObject.registerClass({
GTypeName: 'ClapperPrefsPlayFlagSwitch',
Properties: {
'play-flag': GObject.ParamSpec.int(
'play-flag',
'PlayFlag',
'Value of the gstreamer play flag to toggle',
GObject.ParamFlags.WRITABLE,
1, 4096, 1,
),
},
},
class ClapperPrefsPlayFlagSwitch extends PrefsActionRow
{
_init()
{
super._init(new Gtk.Switch(widgetOpts));
this._flag = 1;
this._doneRealize = false;
}
set play_flag(value)
{
this._flag = value;
}
vfunc_realize()
{
super.vfunc_realize();
if(!this._doneRealize) {
const playFlags = settings.get_int('play-flags');
this.activatable_widget.active = (
(playFlags & this._flag) === this._flag
);
this.activatable_widget.connect(
'notify::active', this._onPlayFlagToggled.bind(this)
);
}
this._doneRealize = true;
}
_onPlayFlagToggled()
{
let playFlags = settings.get_int('play-flags');
if(this.activatable_widget.active)
playFlags |= this._flag;
else
playFlags &= ~this._flag;
settings.set_int('play-flags', playFlags);
}
});
GObject.registerClass({
GTypeName: 'ClapperPrefsSpin',
Properties: {
'spin-adjustment': GObject.ParamSpec.object(
'spin-adjustment',
'GtkAdjustment',
'Custom GtkAdjustment for spin button',
GObject.ParamFlags.WRITABLE,
Gtk.Adjustment
),
},
},
class ClapperPrefsSpin extends PrefsActionRow
{
_init()
{
super._init(new Gtk.SpinButton(widgetOpts));
this._bindProp = 'value';
}
set spin_adjustment(value)
{
this.activatable_widget.set_adjustment(value);
}
});
let PrefsPluginFeature = GObject.registerClass(
class PrefsPluginFeature extends Adw.ActionRow
{
_init(featureObj)
{
super._init({
title: featureObj.name,
});
const enableSwitch = new Gtk.Switch(widgetOpts);
const spinButton = new Gtk.SpinButton(widgetOpts);
spinButton.set_range(0, 512);
spinButton.set_increments(1, 1);
enableSwitch.active = featureObj.enabled;
spinButton.value = featureObj.rank;
this.currentRank = featureObj.rank;
this.add_suffix(enableSwitch);
this.add_suffix(spinButton);
enableSwitch.bind_property('active', spinButton, 'sensitive',
GObject.BindingFlags.SYNC_CREATE
);
enableSwitch.connect('notify::active', this._onSwitchActivate.bind(this));
spinButton.connect('value-changed', this._onValueChanged.bind(this));
}
_updateRanking(data)
{
settings.set_string('plugin-ranking', JSON.stringify(data));
}
_onSwitchActivate(enableSwitch)
{
const { settingsData } = this.get_ancestor(PrefsPluginRankingSubpage);
if(enableSwitch.active)
settingsData[this.title] = this.currentRank;
else if(settingsData[this.title])
delete settingsData[this.title];
this._updateRanking(settingsData);
}
_onValueChanged(spinButton)
{
const { settingsData } = this.get_ancestor(PrefsPluginRankingSubpage);
this.currentRank = spinButton.value;
settingsData[this.title] = this.currentRank;
this._updateRanking(settingsData);
}
});
GObject.registerClass({
GTypeName: 'ClapperPrefsFont',
},
class ClapperPrefsFont extends PrefsActionRow
{
_init()
{
const opts = {
use_font: true,
use_size: true,
};
Object.assign(opts, widgetOpts);
super._init(new Gtk.FontButton(opts));
this._bindProp = 'font';
}
});
GObject.registerClass({
GTypeName: 'ClapperPrefsCombo',
Properties: getCommonProps(),
},
class ClapperPrefsCombo extends Adw.ComboRow
{
_init()
{
super._init();
this._schemaName = null;
}
set schema_name(value)
{
this._schemaName = value;
}
vfunc_realize()
{
super.vfunc_realize();
if(this._schemaName)
settings.bind(this._schemaName, this, 'selected', flags);
this._schemaName = null;
}
});
GObject.registerClass({
GTypeName: 'ClapperPrefsExpander',
Properties: getCommonProps(),
},
class ClapperPrefsExpander extends Adw.ExpanderRow
{
_init()
{
super._init({
show_enable_switch: true,
});
}
set schema_name(value)
{
settings.bind(value, this, 'enable-expansion', flags);
}
});
GObject.registerClass({
GTypeName: 'ClapperPrefsPluginRankingSubpageRow',
},
class ClapperPrefsPluginRankingSubpageRow extends PrefsSubpageRow
{
_createSubpage()
{
return new PrefsPluginRankingSubpage();
}
});
let PrefsPluginExpander = GObject.registerClass(
class ClapperPrefsPluginExpander extends Adw.ExpanderRow
{
_init(plugin)
{
super._init({
title: plugin,
show_enable_switch: false,
});
this.expandSignal = this.connect(
'notify::expanded', this._onExpandedNotify.bind(this)
);
}
_onExpandedNotify()
{
if(!this.expanded)
return;
this.disconnect(this.expandSignal);
this.expandSignal = null;
const { pluginsData } = this.get_ancestor(PrefsPluginRankingSubpage);
pluginsData[this.title].sort((a, b) =>
(a.name > b.name) - (a.name < b.name)
);
const featuresNames = Object.keys(pluginsData[this.title]);
debug(`Adding ${featuresNames.length} features to the list of plugin: ${this.title}`);
for(let featureObj of pluginsData[this.title])
this.add(new PrefsPluginFeature(featureObj));
}
});
let PrefsPluginRankingSubpage = GObject.registerClass({
GTypeName: 'ClapperPrefsPluginRankingSubpage',
Template: `file://${Misc.getClapperPath()}/ui/preferences-plugin-ranking-subpage.ui`,
InternalChildren: ['decoders_group'],
},
class ClapperPrefsPluginRankingSubpage extends Gtk.Box
{
_init()
{
super._init();
this.addTitle('Appearance');
this.addCheckButton('Enable dark theme', 'dark-theme');
if(!Gst.is_initialized())
Gst.init(null);
this.addTitle('Performance');
this.addCheckButton('Render window shadows', 'render-shadows');
const gstRegistry = Gst.Registry.get();
const decoders = gstRegistry.feature_filter(this._decodersFilterCb, false);
const plugins = {};
this.settingsData = {};
/* In case someone messed up gsettings values */
try {
this.settingsData = JSON.parse(settings.get_string('plugin-ranking'));
/* Might be an array in older Clapper versions */
if(Array.isArray(this.settingsData))
this.settingsData = {};
}
catch(err) { /* Ignore */ }
for(let decoder of decoders) {
const pluginName = decoder.get_plugin_name();
/* Do not add unsupported plugins */
switch(pluginName) {
case 'playback':
continue;
default:
break;
}
if(!plugins[pluginName])
plugins[pluginName] = [];
const decName = decoder.get_name();
plugins[pluginName].push({
name: decName,
rank: decoder.get_rank(),
enabled: this.settingsData[decName] != null,
});
}
const pluginsNames = Object.keys(plugins);
debug(`Adding ${pluginsNames.length} found plugins to the list`);
this.pluginsData = pluginsNames.sort().reduce((res, key) =>
(res[key] = plugins[key], res), {}
);
for(let plugin in this.pluginsData)
this._decoders_group.add(new PrefsPluginExpander(plugin));
}
_decodersFilterCb(feature)
{
return (
feature.list_is_type
&& feature.list_is_type(Gst.ELEMENT_FACTORY_TYPE_DECODER)
);
}
_onReturnClicked(button)
{
const prefs = this.get_ancestor(PrefsWindow);
prefs.close_subpage();
}
});
var PrefsWindow = GObject.registerClass({
GTypeName: 'ClapperPrefsWindow',
Template: `file://${Misc.getClapperPath()}/ui/preferences-window.ui`,
},
class ClapperPrefsWindow extends Adw.PreferencesWindow
{
_init(window)
{
super._init({
transient_for: window,
});
this.show();
}
});