diff --git a/src/Objects/VirtualMonitor.vala b/src/Objects/VirtualMonitor.vala index 2fd13b12..3fd28a94 100644 --- a/src/Objects/VirtualMonitor.vala +++ b/src/Objects/VirtualMonitor.vala @@ -20,12 +20,31 @@ */ public class Display.VirtualMonitor : GLib.Object { + public class Transform : Object, Utils.StringRepresentable { + public string string_representation { get; construct; } + public DisplayTransform transform { get; construct; } + + public Transform (DisplayTransform transform) { + Object (transform: transform, string_representation: transform.to_string ()); + } + } + + public class RefreshRate : Object, Utils.StringRepresentable { + public string string_representation { get; construct; } + public MonitorMode mode { get; construct; } + + public RefreshRate (MonitorMode mode) { + Object (mode: mode, string_representation: _("%g Hz").printf (Math.round (mode.frequency))); + } + } + public int x { get; set; } public int y { get; set; } public int current_x { get; set; } public int current_y { get; set; } public double scale { get; set; } - public DisplayTransform transform { get; set; } + public Gtk.SingleSelection available_transforms { get; construct; } + public Gtk.SingleSelection available_refresh_rates { get; construct; } public bool primary { get; set; } public Gee.LinkedList monitors { get; construct; } @@ -55,6 +74,15 @@ public class Display.VirtualMonitor : GLib.Object { public bool is_active { get; set; default = true; } + public DisplayTransform transform { + get { + return (DisplayTransform) available_transforms.selected; + } + set { + available_transforms.selected = value; + } + } + /* * Get the first monitor of the list, handy in non-mirror context. */ @@ -68,8 +96,30 @@ public class Display.VirtualMonitor : GLib.Object { } } + private ListStore available_transforms_store; + private ListStore available_refresh_rates_store; + construct { monitors = new Gee.LinkedList (); + + available_transforms_store = new ListStore (typeof (Transform)); + available_transforms = new Gtk.SingleSelection (available_transforms_store) { + autoselect = true + }; + + for (int i = 0; i <= DisplayTransform.FLIPPED_ROTATION_270; i++) { + available_transforms_store.append (new Transform ((DisplayTransform) i)); + } + + available_refresh_rates_store = new ListStore (typeof (RefreshRate)); + available_refresh_rates = new Gtk.SingleSelection (available_refresh_rates_store) { + autoselect = true + }; + + available_refresh_rates.selection_changed.connect (() => + set_current_mode (((RefreshRate) available_refresh_rates.get_item (available_refresh_rates.selected)).mode)); + + Idle.add_once (update_available_refresh_rates); } public unowned string get_display_name () { @@ -125,6 +175,12 @@ public class Display.VirtualMonitor : GLib.Object { } public void set_current_mode (Display.MonitorMode current_mode) { + var old_current_mode = monitors[0].current_mode; + + if (old_current_mode == current_mode) { + return; + } + if (is_mirror) { monitors.foreach ((_monitor) => { bool mode_found = false; @@ -149,6 +205,49 @@ public class Display.VirtualMonitor : GLib.Object { mode.is_current = mode == current_mode; } } + + update_available_refresh_rates (); + } + + private void update_available_refresh_rates () { + int active_width, active_height; + get_current_mode_size (out active_width, out active_height); + + double[] frequencies = {}; + RefreshRate[] refresh_rates = {}; + uint to_select = 0; + foreach (var mode in get_available_modes ()) { + if (mode.width != active_width || mode.height != active_height) { + continue; + } + + if (mode.frequency in frequencies) { + continue; + } + + bool freq_already_added = false; + foreach (var freq in frequencies) { + if ((mode.frequency - freq).abs () < 1) { + freq_already_added = true; + break; + } + } + + if (freq_already_added) { + continue; + } + + frequencies += mode.frequency; + + refresh_rates += new RefreshRate (mode); + + if (mode.is_current) { + to_select = refresh_rates.length - 1; + } + } + + available_refresh_rates_store.splice (0, available_refresh_rates_store.get_n_items (), refresh_rates); + available_refresh_rates.selected = to_select; } public static string generate_id_from_monitors (MutterReadMonitorInfo[] infos) { diff --git a/src/Utils.vala b/src/Utils.vala index 54989352..e46806d8 100644 --- a/src/Utils.vala +++ b/src/Utils.vala @@ -65,4 +65,28 @@ namespace Display.Utils { return min_scale; } + + public interface StringRepresentable : Object { + public abstract string string_representation { get; construct; } + } + + /** + * Sets up a ListItemFactory that represents its items by a single start aligned label. + * The items have to implement {@link StringRepresentable}. + * Useful for {@link Gtk.DropDown}s. + */ + public static Gtk.ListItemFactory create_string_list_item_factory () { + var string_list_item_factory = new Gtk.SignalListItemFactory (); + string_list_item_factory.setup.connect ((obj) => { + var list_item = (Gtk.ListItem) obj; + list_item.child = new Gtk.Label (null) { xalign = 0 }; + }); + string_list_item_factory.bind.connect ((obj) => { + var list_item = (Gtk.ListItem) obj; + var item = (StringRepresentable) list_item.item; + var scale_label = (Gtk.Label) list_item.child; + scale_label.label = item.string_representation; + }); + return string_list_item_factory; + } } diff --git a/src/Widgets/DisplayWidget.vala b/src/Widgets/DisplayWidget.vala index 82a10b1e..abf209bf 100644 --- a/src/Widgets/DisplayWidget.vala +++ b/src/Widgets/DisplayWidget.vala @@ -57,18 +57,15 @@ public class Display.DisplayWidget : Gtk.Box { public double window_ratio { get; private set; default = 1.0; } public bool connected { get; set; } + private Gtk.Label label; + private Gtk.Button primary_image; private Granite.SwitchModelButton use_switch; private Gtk.ComboBox resolution_combobox; private Gtk.TreeStore resolution_tree_store; - private Gtk.ComboBox rotation_combobox; - private Gtk.ListStore rotation_list_store; - - private Gtk.ComboBox refresh_combobox; - private Gtk.ListStore refresh_list_store; - + private Gtk.DropDown refresh_drop_down; private Gtk.DropDown scale_drop_down; private int real_width = 0; @@ -81,18 +78,6 @@ public class Display.DisplayWidget : Gtk.Box { TOTAL } - private enum RotationColumns { - NAME, - VALUE, - TOTAL - } - - private enum RefreshColumns { - NAME, - VALUE, - TOTAL - } - public DisplayWidget (Display.VirtualMonitor virtual_monitor, string bg_color, string text_color) { Object ( virtual_monitor: virtual_monitor, @@ -116,7 +101,7 @@ public class Display.DisplayWidget : Gtk.Box { primary_image.clicked.connect (() => set_as_primary ()); var virtual_monitor_name = virtual_monitor.get_display_name (); - var label = new Gtk.Label (virtual_monitor_name) { + label = new Gtk.Label (virtual_monitor_name) { halign = CENTER, valign = CENTER, hexpand = true, @@ -141,40 +126,29 @@ public class Display.DisplayWidget : Gtk.Box { resolution_combobox.pack_start (text_renderer, true); resolution_combobox.add_attribute (text_renderer, "text", ResolutionColumns.NAME); - rotation_list_store = new Gtk.ListStore (RotationColumns.TOTAL, typeof (string), typeof (int)); - rotation_combobox = new Gtk.ComboBox.with_model (rotation_list_store) { + var rotation_drop_down = new Gtk.DropDown (virtual_monitor.available_transforms, null) { margin_start = 12, - margin_end = 12 + margin_end = 12, + factory = Utils.create_string_list_item_factory () }; + virtual_monitor.available_transforms.bind_property ("selected", rotation_drop_down, "selected", BIDIRECTIONAL | SYNC_CREATE); var rotation_label = new Granite.HeaderLabel (_("Screen Rotation")) { - mnemonic_widget = rotation_combobox + mnemonic_widget = rotation_drop_down }; - text_renderer = new Gtk.CellRendererText (); - rotation_combobox.pack_start (text_renderer, true); - rotation_combobox.add_attribute (text_renderer, "text", RotationColumns.NAME); - - refresh_list_store = new Gtk.ListStore (RefreshColumns.TOTAL, typeof (string), typeof (Display.MonitorMode)); - refresh_combobox = new Gtk.ComboBox.with_model (refresh_list_store) { + refresh_drop_down = new Gtk.DropDown (virtual_monitor.available_refresh_rates, null) { margin_start = 12, - margin_end = 12 + margin_end = 12, + factory = Utils.create_string_list_item_factory () }; + virtual_monitor.available_refresh_rates.bind_property ("selected", refresh_drop_down, "selected", BIDIRECTIONAL | SYNC_CREATE); + virtual_monitor.available_refresh_rates.items_changed.connect (() => refresh_drop_down.sensitive = virtual_monitor.available_refresh_rates.n_items > 1); var refresh_label = new Granite.HeaderLabel (_("Refresh Rate")) { - mnemonic_widget = refresh_combobox + mnemonic_widget = refresh_drop_down }; - text_renderer = new Gtk.CellRendererText (); - refresh_combobox.pack_start (text_renderer, true); - refresh_combobox.add_attribute (text_renderer, "text", RefreshColumns.NAME); - - for (int i = 0; i <= DisplayTransform.FLIPPED_ROTATION_270; i++) { - Gtk.TreeIter iter; - rotation_list_store.append (out iter); - rotation_list_store.set (iter, RotationColumns.NAME, ((DisplayTransform) i).to_string (), RotationColumns.VALUE, i); - } - // Build resolution menu // First, get list of unique resolutions from available modes. Resolution[] resolutions = {}; @@ -254,8 +228,6 @@ public class Display.DisplayWidget : Gtk.Box { resolution_combobox.sensitive = usable_resolutions > 1; - populate_refresh_rates (); - scale_drop_down = new Gtk.DropDown.from_strings (string_scales) { margin_start = 12, margin_end = 12 @@ -273,9 +245,9 @@ public class Display.DisplayWidget : Gtk.Box { popover_box.append (resolution_label); popover_box.append (resolution_combobox); popover_box.append (rotation_label); - popover_box.append (rotation_combobox); + popover_box.append (rotation_drop_down); popover_box.append (refresh_label); - popover_box.append (refresh_combobox); + popover_box.append (refresh_drop_down); if (!MonitorManager.get_default ().global_scale_required) { popover_box.append (scale_label); @@ -306,14 +278,12 @@ public class Display.DisplayWidget : Gtk.Box { set_primary (virtual_monitor.primary); use_switch.bind_property ("active", resolution_combobox, "sensitive"); - use_switch.bind_property ("active", rotation_combobox, "sensitive"); - use_switch.bind_property ("active", refresh_combobox, "sensitive"); + use_switch.bind_property ("active", rotation_drop_down, "sensitive"); + use_switch.bind_property ("active", refresh_drop_down, "sensitive"); use_switch.bind_property ("active", scale_drop_down, "sensitive"); use_switch.notify["active"].connect (() => { - if (rotation_combobox.active == -1) rotation_combobox.set_active (0); if (resolution_combobox.active == -1) resolution_combobox.set_active (0); - if (refresh_combobox.active == -1) refresh_combobox.set_active (0); if (use_switch.active) { remove_css_class ("disabled"); @@ -351,85 +321,24 @@ public class Display.DisplayWidget : Gtk.Box { } virtual_monitor.set_current_mode (new_mode); - rotation_combobox.set_active (0); - populate_refresh_rates (); configuration_changed (); check_position (); }); - rotation_combobox.changed.connect (() => { + rotation_drop_down.notify["selected"].connect (() => { // Prevent breaking autohide by closing popover popover.popdown (); - Value val; - Gtk.TreeIter iter; - rotation_combobox.get_active_iter (out iter); - rotation_list_store.get_value (iter, RotationColumns.VALUE, out val); - - var transform = (DisplayTransform)((int)val); - virtual_monitor.transform = transform; - - label.css_classes = {""}; - - switch (transform) { - case DisplayTransform.NORMAL: - virtual_monitor.get_current_mode_size (out real_width, out real_height); - label.label = virtual_monitor_name; - break; - case DisplayTransform.ROTATION_90: - virtual_monitor.get_current_mode_size (out real_height, out real_width); - label.add_css_class ("rotate-270"); - label.label = virtual_monitor_name; - break; - case DisplayTransform.ROTATION_180: - virtual_monitor.get_current_mode_size (out real_width, out real_height); - label.add_css_class ("rotate-180"); - label.label = virtual_monitor_name; - break; - case DisplayTransform.ROTATION_270: - virtual_monitor.get_current_mode_size (out real_height, out real_width); - label.add_css_class ("rotate-90"); - label.label = virtual_monitor_name; - break; - case DisplayTransform.FLIPPED: - virtual_monitor.get_current_mode_size (out real_width, out real_height); - label.label = virtual_monitor_name.reverse (); //mirroring simulation, because we can't really mirror the text - break; - case DisplayTransform.FLIPPED_ROTATION_90: - virtual_monitor.get_current_mode_size (out real_height, out real_width); - label.add_css_class ("rotate-270"); - label.label = virtual_monitor_name.reverse (); - break; - case DisplayTransform.FLIPPED_ROTATION_180: - virtual_monitor.get_current_mode_size (out real_width, out real_height); - label.add_css_class ("rotate-180"); - label.label = virtual_monitor_name.reverse (); - break; - case DisplayTransform.FLIPPED_ROTATION_270: - virtual_monitor.get_current_mode_size (out real_height, out real_width); - label.add_css_class ("rotate-90"); - label.label = virtual_monitor_name.reverse (); - break; - } + update_transformed_style (); configuration_changed (); - check_position (); }); - refresh_combobox.changed.connect (() => { + refresh_drop_down.notify["selected"].connect (() => { // Prevent breaking autohide by closing popover popover.popdown (); - Value val; - Gtk.TreeIter iter; - if (refresh_combobox.get_active_iter (out iter)) { - refresh_list_store.get_value (iter, RefreshColumns.VALUE, out val); - Display.MonitorMode new_mode = (Display.MonitorMode) val; - virtual_monitor.set_current_mode (new_mode); - rotation_combobox.set_active (0); - configuration_changed (); - check_position (); - } + configuration_changed (); }); virtual_monitor.notify["scale"].connect (update_selected_scale); @@ -450,11 +359,9 @@ public class Display.DisplayWidget : Gtk.Box { configuration_changed (); }); - rotation_combobox.set_active ((int) virtual_monitor.transform); - on_vm_transform_changed (); - virtual_monitor.modes_changed.connect (on_monitor_modes_changed); - virtual_monitor.notify["transform"].connect (on_vm_transform_changed); + + update_transformed_style (); configuration_changed (); check_position (); @@ -468,65 +375,6 @@ public class Display.DisplayWidget : Gtk.Box { } } - private void populate_refresh_rates () { - refresh_list_store.clear (); - - Gtk.TreeIter iter; - int added = 0; - if (resolution_combobox.get_active_iter (out iter)) { - int active_width, active_height; - if (resolution_combobox.get_active_iter (out iter)) { - resolution_tree_store.get (iter, - ResolutionColumns.WIDTH, out active_width, - ResolutionColumns.HEIGHT, out active_height - ); - } else { - return; - } - - double[] frequencies = {}; - bool refresh_set = false; - foreach (var mode in virtual_monitor.get_available_modes ()) { - if (mode.width != active_width || mode.height != active_height) { - continue; - } - - if (mode.frequency in frequencies) { - continue; - } - - bool freq_already_added = false; - foreach (var freq in frequencies) { - if ((mode.frequency - freq).abs () < 1) { - freq_already_added = true; - break; - } - } - - if (freq_already_added) { - continue; - } - - frequencies += mode.frequency; - - var freq_name = _("%g Hz").printf (Math.roundf ((float)mode.frequency)); - refresh_list_store.append (out iter); - refresh_list_store.set (iter, ResolutionColumns.NAME, freq_name, RefreshColumns.VALUE, mode); - added++; - if (mode.is_current) { - refresh_combobox.set_active_iter (iter); - refresh_set = true; - } - } - - if (!refresh_set) { - refresh_combobox.set_active (0); - } - } - - refresh_combobox.sensitive = added > 1; - } - private void on_monitor_modes_changed () { set_active_resolution_from_current_mode (); } @@ -557,20 +405,51 @@ public class Display.DisplayWidget : Gtk.Box { return result; } - private void on_vm_transform_changed () { - var transform = virtual_monitor.transform; - rotation_list_store.@foreach ((model, path, iter) => { - Value val; - rotation_list_store.get_value (iter, RotationColumns.VALUE, out val); - - var iter_transform = (DisplayTransform)((int)val); - if (iter_transform == transform) { - rotation_combobox.set_active_iter (iter); - return true; - } + private void update_transformed_style () { + label.css_classes = {""}; + + switch (virtual_monitor.transform) { + case DisplayTransform.NORMAL: + virtual_monitor.get_current_mode_size (out real_width, out real_height); + label.label = virtual_monitor.get_display_name (); + break; + case DisplayTransform.ROTATION_90: + virtual_monitor.get_current_mode_size (out real_height, out real_width); + label.add_css_class ("rotate-270"); + label.label = virtual_monitor.get_display_name (); + break; + case DisplayTransform.ROTATION_180: + virtual_monitor.get_current_mode_size (out real_width, out real_height); + label.add_css_class ("rotate-180"); + label.label = virtual_monitor.get_display_name (); + break; + case DisplayTransform.ROTATION_270: + virtual_monitor.get_current_mode_size (out real_height, out real_width); + label.add_css_class ("rotate-90"); + label.label = virtual_monitor.get_display_name (); + break; + case DisplayTransform.FLIPPED: + virtual_monitor.get_current_mode_size (out real_width, out real_height); + label.label = virtual_monitor.get_display_name ().reverse (); //mirroring simulation, because we can't really mirror the text + break; + case DisplayTransform.FLIPPED_ROTATION_90: + virtual_monitor.get_current_mode_size (out real_height, out real_width); + label.add_css_class ("rotate-270"); + label.label = virtual_monitor.get_display_name ().reverse (); + break; + case DisplayTransform.FLIPPED_ROTATION_180: + virtual_monitor.get_current_mode_size (out real_width, out real_height); + label.add_css_class ("rotate-180"); + label.label = virtual_monitor.get_display_name ().reverse (); + break; + case DisplayTransform.FLIPPED_ROTATION_270: + virtual_monitor.get_current_mode_size (out real_height, out real_width); + label.add_css_class ("rotate-90"); + label.label = virtual_monitor.get_display_name ().reverse (); + break; + } - return false; - }); + check_position (); } public void set_primary (bool is_primary) {