🔧 Start porting to GTK4

This commit is contained in:
Simon Schneegans
2023-02-01 05:36:50 +01:00
parent d6c89cd79b
commit 956cef56b4
3 changed files with 483 additions and 415 deletions
+122 -189
View File
@@ -31,9 +31,6 @@ const Me = imports.misc.extensionUtils.getCurrentExtension();
const utils = Me.imports.src.utils;
const ProfileManager = Me.imports.src.ProfileManager.ProfileManager;
// This template widget class is defined at the bottom of this file.
var BurnMyWindowsEffectPage = null;
//////////////////////////////////////////////////////////////////////////////////////////
// The preferences dialog is organized in pages, each of which is loaded from a //
// separate ui file. There's one page with general options, all other paged are loaded //
@@ -48,26 +45,26 @@ var PreferencesDialog = class PreferencesDialog {
// New effects must be registered here and in extension.js.
this._ALL_EFFECTS = [
new Me.imports.src.effects.Apparition.Apparition(),
new Me.imports.src.effects.BrokenGlass.BrokenGlass(),
new Me.imports.src.effects.Doom.Doom(),
new Me.imports.src.effects.EnergizeA.EnergizeA(),
new Me.imports.src.effects.EnergizeB.EnergizeB(),
new Me.imports.src.effects.Fire.Fire(),
new Me.imports.src.effects.Glide.Glide(),
new Me.imports.src.effects.Glitch.Glitch(),
new Me.imports.src.effects.Hexagon.Hexagon(),
new Me.imports.src.effects.Incinerate.Incinerate(),
new Me.imports.src.effects.Matrix.Matrix(),
new Me.imports.src.effects.Pixelate.Pixelate(),
new Me.imports.src.effects.PixelWheel.PixelWheel(),
new Me.imports.src.effects.PixelWipe.PixelWipe(),
new Me.imports.src.effects.Portal.Portal(),
new Me.imports.src.effects.SnapOfDisintegration.SnapOfDisintegration(),
new Me.imports.src.effects.TRexAttack.TRexAttack(),
new Me.imports.src.effects.TVEffect.TVEffect(),
new Me.imports.src.effects.TVGlitch.TVGlitch(),
new Me.imports.src.effects.Wisps.Wisps(),
// new Me.imports.src.effects.Apparition.Apparition(),
// new Me.imports.src.effects.BrokenGlass.BrokenGlass(),
// new Me.imports.src.effects.Doom.Doom(),
// new Me.imports.src.effects.EnergizeA.EnergizeA(),
// new Me.imports.src.effects.EnergizeB.EnergizeB(),
// new Me.imports.src.effects.Fire.Fire(),
// new Me.imports.src.effects.Glide.Glide(),
// new Me.imports.src.effects.Glitch.Glitch(),
// new Me.imports.src.effects.Hexagon.Hexagon(),
// new Me.imports.src.effects.Incinerate.Incinerate(),
// new Me.imports.src.effects.Matrix.Matrix(),
// new Me.imports.src.effects.Pixelate.Pixelate(),
// new Me.imports.src.effects.PixelWheel.PixelWheel(),
// new Me.imports.src.effects.PixelWipe.PixelWipe(),
// new Me.imports.src.effects.Portal.Portal(),
// new Me.imports.src.effects.SnapOfDisintegration.SnapOfDisintegration(),
// new Me.imports.src.effects.TRexAttack.TRexAttack(),
// new Me.imports.src.effects.TVEffect.TVEffect(),
// new Me.imports.src.effects.TVGlitch.TVGlitch(),
// new Me.imports.src.effects.Wisps.Wisps(),
];
// Load all of our resources.
@@ -92,9 +89,6 @@ var PreferencesDialog = class PreferencesDialog {
Gtk.IconTheme.get_default().add_resource_path('/img');
}
// Register the template widgets used in the settings dialog.
this._registerCustomClasses();
// Load the general user interface files.
this._builder = new Gtk.Builder();
this._builder.add_from_resource(`/ui/common/menus.ui`);
@@ -129,12 +123,19 @@ var PreferencesDialog = class PreferencesDialog {
// Maybe the service is masked...
}
this._builder.get_object('profile-power-profile').set_visible(hasPowerProfiles);
let powerProfileRow = this._builder.get_object('profile-power-profile');
if (!utils.isADW()) {
powerProfileRow = powerProfileRow.get_parent().get_parent();
}
powerProfileRow.set_visible(hasPowerProfiles);
// The global color scheme is only available starting with GNOME Shell 42. We hide the
// corresponding settings row on older versions.
this._builder.get_object('profile-color-scheme')
.set_visible(utils.shellVersionIsAtLeast(42, 0));
let colorSchemeRow = this._builder.get_object('profile-color-scheme');
if (!utils.isADW()) {
colorSchemeRow = colorSchemeRow.get_parent().get_parent();
}
colorSchemeRow.set_visible(utils.shellVersionIsAtLeast(42, 0));
this._builder.get_object('profile-choose-app-button').connect('clicked', () => {
Gio.DBus.session.call('org.gnome.Shell',
@@ -154,138 +155,82 @@ var PreferencesDialog = class PreferencesDialog {
}
});
// Starting with GNOME Shell 42, the settings dialog uses libadwaita. We use this to
// create a completely different layout for the settings dialog.
if (utils.isADW()) {
// This is our top-level widget which we will return later.
this._widget = this._builder.get_object('general-prefs');
// This is our top-level widget which we will return later.
this._widget = this._builder.get_object('general-prefs');
// Then add a preferences group for the effect expander rows.
const group = this._builder.get_object('effects-group');
// Then add a preferences group for the effect expander rows.
const group = this._builder.get_object('effects-group');
// This stores all expander rows for the effects. We use this to implement the
// accordion-like behavior of the effect settings.
this._effectRows = [];
// This stores all expander rows for the effects. We use this to implement the
// accordion-functionality of the effect settings.
this._effectRows = [];
// Now add all the rows.
this._ALL_EFFECTS.forEach(effect => {
const [minMajor, minMinor] = effect.getMinShellVersion();
if (utils.shellVersionIsAtLeast(minMajor, minMinor)) {
// Now add all the rows.
this._ALL_EFFECTS.forEach(effect => {
const [minMajor, minMinor] = effect.getMinShellVersion();
if (utils.shellVersionIsAtLeast(minMajor, minMinor)) {
const uiFile = `/ui/${utils.getUIDir()}/${effect.getNick()}.ui`;
const [hasPrefs] = Gio.resources_get_info(uiFile, 0);
const uiFile = `/ui/${utils.getUIDir()}/${effect.getNick()}.ui`;
const [hasPrefs] = Gio.resources_get_info(uiFile, 0);
let row;
if (hasPrefs) {
// Add the settings page to the builder.
this._builder.add_from_resource(uiFile);
let row;
if (hasPrefs) {
// Add the settings page to the builder.
this._builder.add_from_resource(uiFile);
// Add the effect's preferences (if any).
row = this._builder.get_object(`${effect.getNick()}-prefs`);
} else {
row = Adw.ActionRow.new();
}
// On older versions of Adw (e.g. on GNOME Shell <43), the set_use_markup() does
// not yet exist.
if (row.set_use_markup) {
row.set_title('<b>' + effect.getLabel() + '</b>');
row.set_use_markup(true);
} else {
row.set_title(effect.getLabel());
}
// Un-expand any previously expanded effect row. This way we ensure that there
// is only one expanded row at any time.
if (hasPrefs) {
row.connect('notify::expanded', currentRow => {
if (currentRow.get_expanded()) {
this._effectRows.forEach(row => {
if (row != currentRow && row.set_expanded) {
row.set_expanded(false);
}
});
}
});
}
// The preview button.
const previewButton = Gtk.Button.new_from_icon_name('bmw-preview-symbolic');
previewButton.add_css_class('circular');
previewButton.add_css_class('flat');
previewButton.set_tooltip_text(_('Preview this effect'));
previewButton.set_valign(Gtk.Align.CENTER);
row.add_action(previewButton);
previewButton.connect('clicked', () => {
this._previewEffect(effect);
});
// Now add the toggle button for enabling and disabling the effect.
const button = Gtk.Switch.new();
button.set_tooltip_text(_('Use this effect'));
button.set_valign(Gtk.Align.CENTER);
this._builder.expose_object(`${effect.getNick()}-enable-effect`, button);
row.add_prefix(button);
group.add(row);
this._effectRows.push(row);
// Add the effect's preferences (if any).
row = this._builder.get_object(`${effect.getNick()}-prefs`);
} else {
row = Adw.ActionRow.new();
}
});
}
// On older GNOME versions, we use a StackSidebar. The code below works on GTK3 and
// GTK4.
else {
// This is our top-level widget which we will return later.
this._widget = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
});
// On older versions of Adw (e.g. on GNOME Shell <43), the set_use_markup() does
// not yet exist.
if (row.set_use_markup) {
row.set_title('<b>' + effect.getLabel() + '</b>');
row.set_use_markup(true);
} else {
row.set_title(effect.getLabel());
}
const stack = new Gtk.Stack({
transition_type: Gtk.StackTransitionType.SLIDE_UP_DOWN,
});
// Add the general options page.
const generalPage = this._builder.get_object('general-prefs');
generalPage.margin_start = 60;
generalPage.margin_end = 60;
stack.add_titled(generalPage, 'general', _('General Options'));
this.gtkBoxAppend(this._widget, new Gtk.StackSidebar({stack: stack}));
this.gtkBoxAppend(this._widget, stack);
// Add all other effect pages.
this._ALL_EFFECTS.forEach(effect => {
const [minMajor, minMinor] = effect.getMinShellVersion();
if (utils.shellVersionIsAtLeast(minMajor, minMinor)) {
const page = new BurnMyWindowsEffectPage(effect, this);
page.margin_start = 60;
page.margin_end = 60;
page.margin_top = 60;
page.margin_bottom = 60;
const uiFile = `/ui/${utils.getUIDir()}/${effect.getNick()}.ui`;
const [hasPrefs] = Gio.resources_get_info(uiFile);
if (hasPrefs) {
// Add the settings page to the builder.
this._builder.add_from_resource(uiFile);
// Add the effect's preferences (if any).
const prefs = this._builder.get_object(`${effect.getNick()}-prefs`);
if (prefs) {
this.gtkBoxAppend(page, prefs);
// Un-expand any previously expanded effect row. This way we ensure that there
// is only one expanded row at any time.
if (hasPrefs) {
row.connect('notify::expanded', currentRow => {
if (currentRow.get_expanded()) {
this._effectRows.forEach(row => {
if (row != currentRow && row.set_expanded) {
row.set_expanded(false);
}
});
}
}
stack.add_titled(page, effect.getNick(), effect.getLabel());
});
}
});
}
// The preview button.
const previewButton = Gtk.Button.new_from_icon_name('bmw-preview-symbolic');
previewButton.add_css_class('circular');
previewButton.add_css_class('flat');
previewButton.set_tooltip_text(_('Preview this effect'));
previewButton.set_valign(Gtk.Align.CENTER);
row.add_action(previewButton);
previewButton.connect('clicked', () => {
this._previewEffect(effect);
});
// Now add the toggle button for enabling and disabling the effect.
const button = Gtk.Switch.new();
button.set_tooltip_text(_('Use this effect'));
button.set_valign(Gtk.Align.CENTER);
this._builder.expose_object(`${effect.getNick()}-enable-effect`, button);
row.add_prefix(button);
group.add(row);
this._effectRows.push(row);
}
});
// Some things can only be done once the widget is shown as we do not have access to
// the toplevel widget before.
@@ -309,7 +254,7 @@ var PreferencesDialog = class PreferencesDialog {
// Starting with GNOME Shell 42, we have to hack our way through the widget tree
// of the Adw.PreferencesWindow...
if (Adw && utils.shellVersionIsAtLeast(42, 'beta')) {
if (utils.isADW()) {
const header = this._findWidgetByType(window.get_content(), Adw.HeaderBar);
header.pack_start(menu);
header.set_title_widget(this._builder.get_object('profile-button'));
@@ -330,6 +275,7 @@ var PreferencesDialog = class PreferencesDialog {
} else {
window.get_titlebar().pack_start(menu);
window.get_titlebar().set_title_widget(this._builder.get_object('profile-button'));
}
const addURIAction = (name, uri) => {
@@ -473,7 +419,7 @@ var PreferencesDialog = class PreferencesDialog {
const dialogTitle = _('Delete this Profile?');
const dialogSubtitle = _('The current effect profile will be permanently lost.');
if (Adw.MessageDialog) {
if (utils.isADW() && Adw.MessageDialog) {
deleteProfileDialog =
new Adw.MessageDialog({heading: dialogTitle, body: dialogSubtitle});
deleteProfileDialog.add_response('cancel', _('Cancel'));
@@ -495,7 +441,7 @@ var PreferencesDialog = class PreferencesDialog {
deleteProfileDialog.set_hide_on_close(true);
deleteProfileDialog.set_transient_for(window);
deleteProfileDialog.connect('response', (d, response) => {
deleteProfileDialog.connect('response', (dialog, response) => {
if (response == 'delete' || response == Gtk.ResponseType.OK) {
// Stop editing the current profile.
this._builder.get_object('edit-profile-button').active = false;
@@ -512,7 +458,7 @@ var PreferencesDialog = class PreferencesDialog {
this._updateProfileMenu();
}
d.hide();
dialog.hide();
});
const deleteProfileAction = Gio.SimpleAction.new('profile-delete', null);
@@ -557,10 +503,16 @@ var PreferencesDialog = class PreferencesDialog {
return this._widget;
}
// Connects a Gtk.ComboBox (or anything else which has an 'selected' property) to a
// Connects an Adw.ComboRow (or anything else which has an 'selected' property) to a
// settings key. It also binds the corresponding reset button.
bindComboRow(settingsKey) {
this._bind(settingsKey, 'selected');
}
// Connects a Gtk.ComboBox (or anything else which has an 'active' property) to a
// settings key. It also binds the corresponding reset button.
bindComboBox(settingsKey) {
this._bind(settingsKey, 'selected');
this._bind(settingsKey, 'active');
}
// Connects a Gtk.Entry (or anything else which has an 'text' property) to a
@@ -664,11 +616,20 @@ var PreferencesDialog = class PreferencesDialog {
updateProfileButtonMenuIfChanged('profile-power-profile');
this.bindEntry('profile-app');
this.bindComboBox('profile-animation-type');
this.bindComboBox('profile-window-type');
this.bindComboBox('profile-color-scheme');
this.bindComboBox('profile-power-mode');
this.bindComboBox('profile-power-profile');
if (utils.isADW()) {
this.bindComboRow('profile-animation-type');
this.bindComboRow('profile-window-type');
this.bindComboRow('profile-color-scheme');
this.bindComboRow('profile-power-mode');
this.bindComboRow('profile-power-profile');
} else {
this.bindComboBox('profile-animation-type');
this.bindComboBox('profile-window-type');
this.bindComboBox('profile-color-scheme');
this.bindComboBox('profile-power-mode');
this.bindComboBox('profile-power-profile');
}
this.bindSwitch('profile-high-priority');
// Connect all effect settings.
@@ -826,34 +787,6 @@ var PreferencesDialog = class PreferencesDialog {
window.show_all();
}
}
// Initializes template widgets used by the preferences dialog.
_registerCustomClasses() {
// If we are not using libadwaita, each effect page is based on a template widget.
// This template contains the title and the preview button.
if (!utils.isADW() && GObject.type_from_name('BurnMyWindowsEffectPage') == null) {
BurnMyWindowsEffectPage = GObject.registerClass(
{
GTypeName: 'BurnMyWindowsEffectPage',
Template: `resource:///ui/${utils.getUIDir()}/effectPage.ui`,
InternalChildren: ['label', 'button'],
},
class BurnMyWindowsEffectPage extends Gtk.Box { // ------------------------------
_init(effect, dialog) {
super._init();
// Set the effect's name as label.
this._label.label = effect.getLabel();
// Open the preview window once the preview button is clicked.
this._button.connect('clicked', () => {
dialog._previewEffect(effect);
});
}
});
}
}
}
// This is used for setting up the translations.