From 50a3c68a5a495bd036144515a2bf968d53780bcc Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 11 Jul 2022 19:09:28 +0100 Subject: [PATCH 001/142] First compilable version (does not run) --- libcore/utils.vala | 36 +- libcore/widgets/Cellgrid.vala | 51 +- libcore/widgets/{Label.vala => Clue.vala} | 37 +- .../widgets/{Labelbox.vala => Cluebox.vala} | 112 ++--- meson.build | 15 +- src/Application.vala | 2 +- src/Controller.vala | 39 +- src/View.vala | 443 +++++++++--------- 8 files changed, 385 insertions(+), 350 deletions(-) rename libcore/widgets/{Label.vala => Clue.vala} (90%) rename libcore/widgets/{Labelbox.vala => Cluebox.vala} (50%) diff --git a/libcore/utils.vala b/libcore/utils.vala index 18e45a7..eeb879e 100644 --- a/libcore/utils.vala +++ b/libcore/utils.vala @@ -280,9 +280,14 @@ namespace Gnonograms.Utils { dialog.set_default_response (Gtk.ResponseType.NO); } - dialog.set_position (Gtk.WindowPosition.MOUSE); - int response = dialog.run (); - dialog.destroy (); + // dialog.set_position (Gtk.WindowPosition.MOUSE); + Gtk.ResponseType response = Gtk.ResponseType.NO; + dialog.response.connect ((resp) => { + dialog.destroy (); + response = (Gtk.ResponseType)resp; + }); + + dialog.show (); return response; } @@ -309,7 +314,7 @@ namespace Gnonograms.Utils { public static string? get_open_save_path (Gtk.Window? parent, string dialogname, bool save, - string start_path, + string start_folder_path, string basename) { string? file_path = null; string button_label = save ? _("Save") : _("Open"); @@ -325,13 +330,13 @@ namespace Gnonograms.Utils { dialog.set_modal (true); try { if (save) { - dialog.set_current_folder_file (File.new_for_path (start_path)); + dialog.set_current_folder (File.new_for_path (start_folder_path)); if (basename != null) { dialog.set_current_name (basename); } } else { try { - dialog.set_current_folder_file (File.new_for_path (start_path)); + dialog.set_current_folder (File.new_for_path (start_folder_path)); } catch (Error e) { warning ("Error setting current folder: %s", e.message); } @@ -340,19 +345,24 @@ namespace Gnonograms.Utils { warning ("Error configuring FileChooser dialog: %s", e.message); } - var response = dialog.run (); - if (response == Gtk.ResponseType.ACCEPT) { - file_path = dialog.get_filename (); - } + dialog.response.connect ((response) => { + if (response == Gtk.ResponseType.ACCEPT) { + file_path = dialog.get_file ().get_path (); + } + + dialog.destroy (); + }); + + dialog.show (); - dialog.destroy (); return file_path; } - public Gdk.Rectangle get_monitor_area (Gdk.Screen screen, Gdk.Window window) { + public Gdk.Rectangle get_monitor_area (Gdk.Surface surface) { var display = Gdk.Display.get_default (); - var monitor = display.get_monitor_at_window (window); + var monitor = display.get_monitor_at_surface (surface); + // var monitor = display.get_monitor_at_window (window); return monitor.get_geometry (); } } diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 503db44..a94333d 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -19,6 +19,9 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { public signal void cursor_moved (Cell from, Cell to); + public signal void button_pressed (uint button, bool double_click); + public signal void button_released (); + public signal void leave (); public View view { get; construct; } public Cell current_cell { get; set; } @@ -92,19 +95,23 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { cell_pattern_type = CellPatternType.CELL; set_colors (); + var motion_controller = new Gtk.EventControllerMotion (); + add_controller (motion_controller); + motion_controller.motion.connect (on_pointer_moved); + motion_controller.leave.connect (on_leave_notify); - this.add_events ( - Gdk.EventMask.BUTTON_PRESS_MASK | - Gdk.EventMask.BUTTON_RELEASE_MASK | - Gdk.EventMask.POINTER_MOTION_MASK | - Gdk.EventMask.KEY_PRESS_MASK | - Gdk.EventMask.KEY_RELEASE_MASK | - Gdk.EventMask.LEAVE_NOTIFY_MASK - ); + var button_controller = new Gtk.GestureClick (); + add_controller (button_controller); + button_controller.pressed.connect ((n_press, x, y) => { + var button_event = (Gdk.ButtonEvent)(button_controller.get_current_event ()); + button_pressed (button_event.get_button (), n_press > 1); + }); + button_controller.released.connect ((n_press, x, y) => { + button_released (); + }); - motion_notify_event.connect (on_pointer_moved); - draw.connect (on_draw_event); - leave_notify_event.connect (on_leave_notify); + // draw.connect (on_draw_event); + set_draw_func (draw_func); notify["current-cell"].connect (() => { queue_draw (); @@ -152,7 +159,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { set_size_request (cols * view.cell_size + (int)MINOR_GRID_LINE_WIDTH, rows * view.cell_size + (int)MINOR_GRID_LINE_WIDTH); } - private bool on_draw_event (Cairo.Context cr) { + private void draw_func (Gtk.DrawingArea drawing_area, Cairo.Context cr, int x, int y) { dirty = false; if (array != null) { @@ -164,18 +171,17 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } draw_grid (cr); - return true; } - private bool on_pointer_moved (Gdk.EventMotion e) { - if (draw_only || e.x < 0 || e.y < 0) { - return false; + private void on_pointer_moved (double dx, double dy) { + if (draw_only || dx < 0 || dy < 0) { + return; } /* Calculate which cell the pointer is over */ - uint r = ((uint)((e.y) / cell_height)); - uint c = ((uint)(e.x / cell_width)); + uint r = ((uint)((dy) / cell_height)); + uint c = ((uint)(dx / cell_width)); if (r >= rows || c >= cols) { - return true; + return; } /* Construct cell beneath pointer */ Cell cell = {r, c, array.get_data_from_rc (r, c)}; @@ -183,7 +189,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { update_current_cell (cell); } - return true; + return; } private void update_current_cell (Cell target) { @@ -296,10 +302,11 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } } - private bool on_leave_notify () { + private void on_leave_notify () { previous_cell = NULL_CELL; current_cell = NULL_CELL; - return false; + leave (); + return; } private class CellPattern { diff --git a/libcore/widgets/Label.vala b/libcore/widgets/Clue.vala similarity index 90% rename from libcore/widgets/Label.vala rename to libcore/widgets/Clue.vala index 7d5cd9e..824b6ce 100644 --- a/libcore/widgets/Label.vala +++ b/libcore/widgets/Clue.vala @@ -17,7 +17,8 @@ * Author: Jeremy Wootten */ -class Gnonograms.Clue : Gtk.Label { +class Gnonograms.Clue : Gtk.Widget { + public Gtk.Label label { get; construct; } private uint _n_cells; public uint n_cells { set { @@ -43,14 +44,14 @@ class Gnonograms.Clue : Gtk.Label { } } - private string _clue; /* text of clue in horizontal form */ - public string clue { + private string _text; /* text of clue in horizontal form */ + public string text { get { - return _clue; + return _text; } set { - _clue = value; + _text = value; clue_blocks = Utils.block_struct_array_from_clue (value); update_markup (); } @@ -63,18 +64,18 @@ class Gnonograms.Clue : Gtk.Label { public Clue (bool _vertical_text) { Object ( - vertical_text: _vertical_text, - xalign: _vertical_text ? (float)0.5 : (float)1.0, - yalign: _vertical_text ? (float)1.0 : (float)0.5, - clue: "0", - has_tooltip: true, - use_markup: true, - margin: 0, - expand: true + vertical_text: _vertical_text ); } construct { + label = new Gtk.Label ("0") { + xalign = _vertical_text ? (float)0.5 : (float)1.0, + yalign = vertical_text ? (float)1.0 : (float)0.5, + has_tooltip = true, + use_markup = true + }; + realize.connect_after (() => { update_markup (); }); @@ -214,7 +215,7 @@ class Gnonograms.Clue : Gtk.Label { } } } - } else if (clue != "0") { /* Zero grid blocks should only occur if cellstates all "empty" */ + } else if (text != "0") { /* Zero grid blocks should only occur if cellstates all "empty" */ errors++; } @@ -226,13 +227,13 @@ class Gnonograms.Clue : Gtk.Label { } private void update_markup () { - set_markup ("".printf (_fontsize) + get_markup () + ""); + label.set_markup ("".printf (_fontsize) + get_markup () + ""); update_tooltip (); } private void update_tooltip () { - set_tooltip_markup ("".printf (_fontsize) + - _("Freedom = %u").printf (n_cells - Utils.blockextent_from_clue (_clue)) + + label.set_tooltip_markup ("".printf (_fontsize) + + _("Freedom = %u").printf (n_cells - Utils.blockextent_from_clue (_text)) + "" ); } @@ -241,7 +242,7 @@ class Gnonograms.Clue : Gtk.Label { string attrib = ""; string weight = "bold"; string strikethrough = "false"; - bool warn = get_style_context ().has_class ("warn"); + bool warn = label.has_css_class ("warn"); StringBuilder sb = new StringBuilder (""); foreach (Block clue_block in clue_blocks) { diff --git a/libcore/widgets/Labelbox.vala b/libcore/widgets/Cluebox.vala similarity index 50% rename from libcore/widgets/Labelbox.vala rename to libcore/widgets/Cluebox.vala index 0d3c8df..d50f7b9 100644 --- a/libcore/widgets/Labelbox.vala +++ b/libcore/widgets/Cluebox.vala @@ -1,4 +1,4 @@ -/* Labelbox.vala + /* * Copyright (C) 2010 - 2021 Jeremy Wootten * This program is free software: you can redistribute it and/or modify @@ -17,34 +17,33 @@ * Author: Jeremy Wootten */ -public class Gnonograms.LabelBox : Gtk.Grid { +public class Gnonograms.ClueBox : Gtk.Box { public View view { get; construct; } - private uint n_labels = 0; + private uint n_clues = 0; private uint n_cells = 0; - - public LabelBox (Gtk.Orientation _orientation, View view) { - Object (view: view, - column_homogeneous: true, - row_homogeneous: true, - column_spacing: 0, - row_spacing: 0, - orientation: _orientation, - expand: false + private List clues = null; + + public ClueBox (Gtk.Orientation _orientation, View view) { + Object ( + view: view, + homogeneous: true, + spacing: 0, + orientation: _orientation ); } construct { view.notify["cell-size"].connect (() => { - get_children ().foreach ((w) => { - ((Gnonograms.Clue)w).cell_size = view.cell_size; + clues.foreach ((clue) => { + clue.cell_size = view.cell_size; }); set_size (); }); view.controller.notify ["dimensions"].connect (() => { - var new_n_labels = orientation == Gtk.Orientation.HORIZONTAL ? + var new_n_clues = orientation == Gtk.Orientation.HORIZONTAL ? view.controller.dimensions.width : view.controller.dimensions.height; @@ -52,97 +51,98 @@ public class Gnonograms.LabelBox : Gtk.Grid { view.controller.dimensions.height : view.controller.dimensions.width; - if (new_n_labels != n_labels || new_n_cells != n_cells) { - n_labels = new_n_labels; + if (new_n_clues != n_clues || new_n_cells != n_cells) { + n_clues = new_n_clues; n_cells = new_n_cells; - change_n_labels (); + change_n_clues (); } }); - - show_all (); } - private Gnonograms.Clue? get_label (uint index) { - var n_children = get_children ().length (); - if (index >= n_children) { + private Gnonograms.Clue? get_clue (uint index) { + // var n_children = get_children ().length (); + if (index >= n_clues) { return null; } else { - return (Gnonograms.Clue)(get_children ().nth_data (n_children - index - 1)); + return clues.nth_data (n_clues - index - 1); } } public string[] get_clues () { - string[] clues = new string [n_labels]; - var index = n_labels; - foreach (var widget in get_children ()) { // Delivers widgets in reverse order they were added + string[] clue_text = new string [n_clues]; + var index = n_clues; + clues.@foreach ((clue) => { // Delivers widgets in reverse order they were added index--; - clues[index] = ((Clue)widget).clue; - } + clue_text[index] = clue.text; + }); - return clues; + return clue_text; } public void highlight (uint index, bool is_highlight) { - var label = get_label (index); - if (label != null) { - label.highlight (is_highlight); + var clue = get_clue (index); + if (clue != null) { + clue.highlight (is_highlight); } } public void unhighlight_all () { - get_children ().foreach ((w) => { - ((Gnonograms.Clue)w).highlight (false); + clues.foreach ((clue) => { + clue.highlight (false); }); } - public void update_label_text (uint index, string? txt) { - var label = get_label (index); - if (label != null) { - label.clue = txt ?? _(BLANKLABELTEXT); + public void update_clue_text (uint index, string? txt) { + var clue = get_clue (index); + if (clue != null) { + clue.text = txt ?? _(BLANKLABELTEXT); } } public void clear_formatting (uint index) { - var label = get_label (index); - if (label != null) { - label.clear_formatting (); + var clue = get_clue (index); + if (clue != null) { + clue.clear_formatting (); } } - public void update_label_complete (uint index, Gee.List grid_blocks) { - var label = get_label (index); - if (label != null) { - label.update_complete (grid_blocks); + public void update_clue_complete (uint index, Gee.List grid_blocks) { + var clue = get_clue (index); + if (clue != null) { + clue.update_complete (grid_blocks); } } - private void change_n_labels () { - foreach (var child in get_children ()) { - child.destroy (); - } + private void change_n_clues () { + clues.@foreach ((clue) => { + clue.destroy (); + }); + + clues = null; - for (var i = 0; i < n_labels; i++) { - var label = new Clue (orientation == Gtk.Orientation.HORIZONTAL) { + for (var i = 0; i < n_clues; i++) { + var clue = new Clue (orientation == Gtk.Orientation.HORIZONTAL) { n_cells = this.n_cells, cell_size = view.cell_size }; - add (label); + append (clue); + clues.append (clue); } set_size (); - show_all (); + // show_all (); } private void set_size () { int width = (int)(orientation == Gtk.Orientation.HORIZONTAL ? - n_labels * view.cell_size : + n_clues * view.cell_size : n_cells * view.cell_size * GRID_LABELBOX_RATIO ); int height = (int)(orientation == Gtk.Orientation.HORIZONTAL ? n_cells * view.cell_size * GRID_LABELBOX_RATIO : - n_labels * view.cell_size + n_clues * view.cell_size ); set_size_request (width, height); diff --git a/meson.build b/meson.build index 34782c9..3faf5ea 100644 --- a/meson.build +++ b/meson.build @@ -10,11 +10,6 @@ if get_option('with_debugging') add_project_arguments('--define=WITH_DEBUGGING', language: 'vala') endif -GTK3 = dependency('gtk+-3.0', version: '>=3.24') -GEE = dependency('gee-0.8', version: '>=0.20') -GRANITE = dependency('granite', version: '>=6.1.0') -HDY = dependency('libhandy-1', version: '>=1.2.0') - gnome = import('gnome') gresource = gnome.compile_resources( 'gresource', @@ -41,8 +36,8 @@ executable ( 'src/View.vala', 'src/services/RandomPatternGenerator.vala', 'src/services/RandomGameGenerator.vala', - 'libcore/widgets/Labelbox.vala', - 'libcore/widgets/Label.vala', + 'libcore/widgets/Cluebox.vala', + 'libcore/widgets/Clue.vala', 'libcore/widgets/Cellgrid.vala', 'libcore/utils.vala', 'libcore/Model.vala', @@ -59,10 +54,8 @@ executable ( config_file, dependencies : [ - GTK3, - GEE, - GRANITE, - HDY + dependency('granite-7'), + dependency('libadwaita-1') ], install: true diff --git a/src/Application.vala b/src/Application.vala index 8dbd13f..e97245a 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -85,7 +85,7 @@ public static int main (string[] args) { var opt_context = new OptionContext (N_("[Gnonogram Puzzle File (.gno)]")); opt_context.set_translation_domain (Config.APP_ID); opt_context.add_main_entries (OPTIONS, Config.APP_ID); - opt_context.add_group (Gtk.get_option_group (true)); + // opt_context.add_group (Gtk.get_option_group (true)); opt_context.parse (ref args); } catch (OptionError e) { printerr ("error: %s\n", e.message); diff --git a/src/Controller.vala b/src/Controller.vala index 8538f1a..a1327ae 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -51,8 +51,11 @@ public class Gnonograms.Controller : GLib.Object { view = new View (model, this); history = new Gnonograms.History (); - view.delete_event.connect (on_view_deleted); - view.configure_event.connect (on_view_configure); + view.close_request.connect (on_view_deleted); + view.notify["default-width"].connect (on_view_configure); + view.notify["default-height"].connect (on_view_configure); + view.notify["maximized"].connect (on_view_configure); + view.notify["fullscreened"].connect (on_view_configure); #if WITH_DEBUGGING view.debug_request.connect (on_debug_request); #endif @@ -114,7 +117,7 @@ public class Gnonograms.Controller : GLib.Object { settings.bind ("clue-help", view, "strikeout-complete", SettingsBindFlags.DEFAULT); } - view.show_all (); + // view.show_all (); view.present (); restore_settings (); @@ -158,7 +161,7 @@ public class Gnonograms.Controller : GLib.Object { private void clear () { model.clear (); - view.update_labels_from_solution (); + view.update_clues_from_solution (); clear_history (); is_readonly = true; // Force Save As when saving new design } @@ -187,7 +190,7 @@ public class Gnonograms.Controller : GLib.Object { if (success) { model.set_solution_from_array (generator.get_solution ()); game_state = GameState.SOLVING; - view.update_labels_from_solution (); + view.update_clues_from_solution (); view.game_grade = generator.solution_grade; } else { clear (); @@ -224,12 +227,12 @@ public class Gnonograms.Controller : GLib.Object { private void restore_settings () { if (saved_state != null) { - int x, y, cell_size; + int cell_size; saved_state.get ("cell-size", "i", out cell_size); - saved_state.get ("window-position", "(ii)", out x, out y); + // saved_state.get ("window-position", "(ii)", out x, out y); current_game_path = saved_state.get_string ("current-game-path"); view.cell_size = cell_size; - window.move (x, y); + // window.move (x, y); } else { /* Error normally thrown running uninstalled */ critical ("Unable to restore settings - using defaults"); /* Maybe running uninstalled */ @@ -388,8 +391,8 @@ public class Gnonograms.Controller : GLib.Object { if (reader.has_solution) { view.game_grade = reader.difficulty; } else if (reader.has_row_clues && reader.has_col_clues) { - view.update_labels_from_string_array (reader.row_clues, false); - view.update_labels_from_string_array (reader.col_clues, true); + view.update_clues_from_string_array (reader.row_clues, false); + view.update_clues_from_string_array (reader.col_clues, true); } else { reader.err_msg = (_("Clues missing")); return false; @@ -397,7 +400,7 @@ public class Gnonograms.Controller : GLib.Object { if (reader.has_solution) { model.set_solution_data_from_string_array (reader.solution[0 : dimensions.height]); - view.update_labels_from_solution (); /* Ensure completeness correctly set */ + view.update_clues_from_solution (); /* Ensure completeness correctly set */ } if (reader.name.length > 1 && reader.name != "") { @@ -510,13 +513,13 @@ public class Gnonograms.Controller : GLib.Object { private bool on_view_deleted () { quit (); - return false; + return Gdk.EVENT_PROPAGATE; } private uint configure_id = 0; - private bool on_view_configure () { + private void on_view_configure () { if (saved_state == null) { - return false; + return; } if (configure_id != 0) { @@ -525,16 +528,12 @@ public class Gnonograms.Controller : GLib.Object { configure_id = Timeout.add (100, () => { configure_id = 0; - - int x_pos, y_pos; - view.get_position (out x_pos, out y_pos); - saved_state.set ("window-position", "(ii)", x_pos, y_pos); saved_state.set ("cell-size", "i", view.cell_size); - return false; + return Source.REMOVE; }); - return false; + return; } public void save_game () { diff --git a/src/View.vala b/src/View.vala index efe04d3..a0cbdc8 100644 --- a/src/View.vala +++ b/src/View.vala @@ -17,8 +17,8 @@ * Author: Jeremy Wootten */ -public class Gnonograms.View : Hdy.ApplicationWindow { - private class ProgressIndicator : Gtk.Grid { +public class Gnonograms.View : Gtk.ApplicationWindow { + private class ProgressIndicator : Gtk.Box { private Gtk.Spinner spinner; private Gtk.Button cancel_button; private Gtk.Label label; @@ -34,8 +34,8 @@ public class Gnonograms.View : Hdy.ApplicationWindow { public ProgressIndicator () { Object ( orientation: Gtk.Orientation.HORIZONTAL, - column_homogeneous: false, - column_spacing: 6, + homogeneous: false, + spacing: 6, valign: Gtk.Align.CENTER ); } @@ -43,22 +43,24 @@ public class Gnonograms.View : Hdy.ApplicationWindow { construct { spinner = new Gtk.Spinner (); label = new Gtk.Label (null); - label.get_style_context ().add_class (Granite.STYLE_CLASS_H3_LABEL); + label.add_css_class (Granite.STYLE_CLASS_H3_LABEL); - add (label); - add (spinner); + append (label); + append (spinner); cancel_button = new Gtk.Button (); - var img = new Gtk.Image.from_icon_name ("process-stop-symbolic", Gtk.IconSize.LARGE_TOOLBAR); - img.set_tooltip_text (_("Cancel solving")); - cancel_button.image = img; - cancel_button.no_show_all = true; - cancel_button.get_style_context ().add_class ("warn"); - img.get_style_context ().add_class ("warn"); + var img = new Gtk.Image.from_icon_name ("process-stop-symbolic") { + icon_size = Gtk.IconSize.LARGE, + tooltip_text = _("Cancel solving") + }; + cancel_button.child = img; + // cancel_button.no_show_all = true; + cancel_button.add_css_class ("warn"); + img.add_css_class ("warn"); - add (cancel_button); + append (cancel_button); - show_all (); + // show_all (); cancel_button.clicked.connect (() => { if (cancellable != null) { @@ -86,18 +88,26 @@ public class Gnonograms.View : Hdy.ApplicationWindow { } private class HeaderButton : Gtk.Button { - construct { - valign = Gtk.Align.CENTER; - } - + public Gtk.Image image { get; construct; } public HeaderButton (string icon_name, string action_name, string text) { Object ( action_name: action_name, tooltip_markup: Granite.markup_accel_tooltip ( - View.app.get_accels_for_action (action_name), text), - image: new Gtk.Image.from_icon_name (icon_name, Gtk.IconSize.LARGE_TOOLBAR) + View.app.get_accels_for_action (action_name), text + ) ); } + + construct { + valign = Gtk.Align.CENTER; + + image = new Gtk.Image.from_icon_name (icon_name) { + icon_size = Gtk.IconSize.LARGE + }; + + child = image; + } + } private class RestartButton : HeaderButton { @@ -108,12 +118,11 @@ public class Gnonograms.View : Hdy.ApplicationWindow { notify["restart-destructive"].connect (() => { if (restart_destructive) { - image.get_style_context ().add_class ("warn"); - image.get_style_context ().remove_class ("dim"); + image.add_css_class ("warn"); + image.remove_css_class ("dim"); } else { - image.get_style_context ().remove_class ("warn"); - image.get_style_context ().add_class ("dim"); - + image.remove_css_class ("warn"); + image.add_css_class ("dim"); } }); @@ -125,79 +134,123 @@ public class Gnonograms.View : Hdy.ApplicationWindow { } } - private class AppMenu : Gtk.MenuButton { - private class GradeChooser : Gtk.ComboBoxText { + private class AppMenu : Gtk.Widget { // MenuButton + private class GradeChooser : Gtk.Widget { // ComboBoxText + public Gtk.ComboBoxText combo_box_text { get; construct; } public Difficulty grade { get { - return (Difficulty)(int.parse (active_id)); + return (Difficulty)(int.parse (combo_box_text.active_id)); } set { - active_id = ((uint)value).clamp (MIN_GRADE, Difficulty.MAXIMUM).to_string (); + combo_box_text.active_id = ((uint)value).clamp (MIN_GRADE, Difficulty.MAXIMUM).to_string (); } } + + static construct { + set_layout_manager_type (typeof (Gtk.BinLayout)); + } - public GradeChooser () { - Object ( - expand: false - ); - + construct { + combo_box_text = new Gtk.ComboBoxText (); + combo_box_text.set_parent (this); + foreach (Difficulty d in Difficulty.all_human ()) { - append (((uint)d).to_string (), d.to_string ()); + combo_box_text.append (((uint)d).to_string (), d.to_string ()); } } } - private class DimensionSpinButton : Gtk.SpinButton { - public DimensionSpinButton () { - Object ( - adjustment: new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), - climb_rate: 5.0, - digits: 0, - snap_to_ticks: true, - orientation: Gtk.Orientation.HORIZONTAL, - margin_top: 3, - margin_bottom: 3, - width_chars: 3, - can_focus: true - ); + private class DimensionSpinButton : Gtk.Widget { // SpinButton + public Gtk.SpinButton spin_button { get; construct; } + // public DimensionSpinButton () { + // Object ( + // adjustment: new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), + // climb_rate: 5.0, + // digits: 0, + // snap_to_ticks: true, + // orientation: Gtk.Orientation.HORIZONTAL, + // margin_top: 3, + // margin_bottom: 3, + // width_chars: 3, + // can_focus: true + // ); + // } + + public uint size { + get { + return (uint)(spin_button.@value); + } + + set { + spin_button.@value = value; + } + } + + static construct { + set_layout_manager_type (typeof (Gtk.BinLayout)); + } + + construct { + spin_button = new Gtk.SpinButton ( + new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), + 5.0, + 0 + ) { + snap_to_ticks = true, + orientation = Gtk.Orientation.HORIZONTAL, + margin_top = 3, + margin_bottom = 3, + width_chars = 3, + can_focus = true + }; + + spin_button.set_parent (this); } } + public Gtk.MenuButton menu_button { get; construct; } public unowned Controller controller { get; construct; } public AppMenu (Controller controller) { Object ( - image: new Gtk.Image.from_icon_name ("open-menu", Gtk.IconSize.LARGE_TOOLBAR), - tooltip_text: _("Options"), + // tooltip_text: _("Options"), controller: controller ); } + static construct { + set_layout_manager_type (typeof (Gtk.BinLayout)); + } + construct { - var zoom_out_button = new Gtk.Button.from_icon_name ("zoom-out-symbolic", Gtk.IconSize.MENU) { + menu_button = new Gtk.MenuButton () { + tooltip_text = _("Options") + }; + + menu_button.set_parent (this); + + var zoom_out_button = new Gtk.Button.from_icon_name ("zoom-out-symbolic") { action_name = ACTION_PREFIX + ACTION_ZOOM_OUT, tooltip_markup = Granite.markup_accel_tooltip ( app.get_accels_for_action (ACTION_PREFIX + ACTION_ZOOM_OUT), _("Zoom out") ) }; - var zoom_in_button = new Gtk.Button.from_icon_name ("zoom-in-symbolic", Gtk.IconSize.MENU) { + var zoom_in_button = new Gtk.Button.from_icon_name ("zoom-in-symbolic") { action_name = ACTION_PREFIX + ACTION_ZOOM_IN, tooltip_markup = Granite.markup_accel_tooltip ( app.get_accels_for_action (ACTION_PREFIX + ACTION_ZOOM_IN), _("Zoom in") ) }; - var size_grid = new Gtk.Grid () { - column_homogeneous = true, + var size_grid = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6) { + homogeneous = true, hexpand = true, - margin= 12 }; - size_grid.get_style_context ().add_class (Gtk.STYLE_CLASS_LINKED); - - size_grid.add (zoom_out_button); - size_grid.add (zoom_in_button); + // size_grid.add_css_class (Gtk.STYLE_CLASS_LINKED); + size_grid.append (zoom_out_button); + size_grid.append (zoom_in_button); var grade_setting = new GradeChooser (); var row_setting = new DimensionSpinButton (); @@ -208,47 +261,59 @@ public class Gnonograms.View : Hdy.ApplicationWindow { var settings_grid = new Gtk.Grid () { orientation = Gtk.Orientation.VERTICAL, - margin = 12, row_spacing = 6, column_homogeneous = false }; - settings_grid.attach (new SettingLabel (_("Name:")), 0, 0, 1); + settings_grid.attach (new Gtk.Label (_("Name:")), 0, 0, 1); settings_grid.attach (title_setting, 1, 0, 3); - settings_grid.attach (new SettingLabel (_("Difficulty:")), 0, 1, 1); + settings_grid.attach (new Gtk.Label (_("Difficulty:")), 0, 1, 1); settings_grid.attach (grade_setting, 1, 1, 3); - settings_grid.attach (new SettingLabel (_("Rows:")), 0, 2, 1); + settings_grid.attach (new Gtk.Label (_("Rows:")), 0, 2, 1); settings_grid.attach (row_setting, 1, 2, 1); - settings_grid.attach (new SettingLabel (_("Columns:")), 0, 3, 1); + settings_grid.attach (new Gtk.Label (_("Columns:")), 0, 3, 1); settings_grid.attach (column_setting, 1, 3, 1); - var main_grid = new Gtk.Grid () {orientation = Gtk.Orientation.VERTICAL}; - main_grid.add (size_grid); - main_grid.add (settings_grid); + var main_grid = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); + main_grid.append (size_grid); + main_grid.append (settings_grid); + + var app_popover = new AppPopover () { + child = main_grid + }; - var app_popover = new AppPopover (); - app_popover.add (main_grid); - set_popover (app_popover); + var popover_key_controller = new Gtk.EventControllerKey (); + main_grid.add_controller (popover_key_controller); + popover_key_controller.key_pressed.connect ((keyval, keycode, state) => { + app_popover.cancelled = (keyval == Gdk.Key.Escape); + + if (keyval == Gdk.Key.KP_Enter || keyval == Gdk.Key.Return) { + app_popover.hide (); + } + }); + + menu_button.set_popover (app_popover); + menu_button.child = new Gtk.Image.from_icon_name ("open-menu") { icon_size = Gtk.IconSize.LARGE }; app_popover.apply_settings.connect (() => { controller.generator_grade = grade_setting.grade; - controller.dimensions = {(uint)column_setting.@value, (uint)row_setting.@value}; + controller.dimensions = {column_setting.size, row_setting.size}; controller.game_name = title_setting.text; // Must come after changing dimensions }); - toggled.connect (() => { /* Allow parent to set values first */ - if (active) { + menu_button.activate.connect (() => { /* Allow parent to set values first */ + // if (active) { grade_setting.grade = controller.generator_grade; - row_setting.value = (double)(controller.dimensions.height); - column_setting.value = (double)(controller.dimensions.width); + row_setting.size = controller.dimensions.height; + column_setting.size = controller.dimensions.width; title_setting.text = controller.game_name; - popover.show_all (); - } + // popover.show_all (); + // } }); } /** Popover that can be cancelled with Escape and closed by Enter **/ private class AppPopover : Gtk.Popover { - private bool cancelled = false; + public bool cancelled { get; set; } public signal void apply_settings (); public signal void cancel (); @@ -262,24 +327,6 @@ public class Gnonograms.View : Hdy.ApplicationWindow { cancelled = false; }); - - key_press_event.connect ((event) => { - cancelled = (event.keyval == Gdk.Key.Escape); - - if (event.keyval == Gdk.Key.KP_Enter || event.keyval == Gdk.Key.Return) { - hide (); - } - }); - } - } - - private class SettingLabel : Gtk.Label { - public SettingLabel (string text) { - Object ( - label: text, - xalign: 1.0f, - margin_end: 6 - ); } } } @@ -366,17 +413,18 @@ public class Gnonograms.View : Hdy.ApplicationWindow { }; public static Gtk.Application app; - private LabelBox row_clue_box; - private LabelBox column_clue_box; + private Gtk.EventControllerKey key_controller; + private ClueBox row_clue_box; + private ClueBox column_clue_box; private CellGrid cell_grid; private ProgressIndicator progress_indicator; private AppMenu app_menu; private CellState drawing_with_state = CellState.UNDEFINED; - private Hdy.HeaderBar header_bar; - private Granite.Widgets.Toast toast; + private Gtk.HeaderBar header_bar; + // private Adw.Toast toast; private Granite.ModeSwitch mode_switch; private Gtk.Grid main_grid; - private Gtk.Overlay overlay; + private Adw.ToastOverlay toast_overlay; private Gtk.Stack progress_stack; private Gtk.Label title_label; private Gtk.Label grade_label; @@ -445,18 +493,18 @@ warning ("WITH DEBUGGING"); try { var css_provider = new Gtk.CssProvider (); css_provider.load_from_resource ("com/github/jeremypw/gnonograms/Application.css"); - Gtk.StyleContext.add_provider_for_screen ( - Gdk.Screen.get_default (), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + Gtk.StyleContext.add_provider_for_display ( + Gdk.Display.get_default (), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ); } catch (Error e) { warning ("Error adding css provider: %s", e.message); } - Hdy.init (); + // Hdy.init (); } construct { - weak Gtk.IconTheme default_theme = Gtk.IconTheme.get_default (); + weak Gtk.IconTheme default_theme = Gtk.IconTheme.get_for_display (Gdk.Display.get_default ()); default_theme.add_resource_path ("/com/github/jeremypw/gnonograms"); var view_actions = new GLib.SimpleActionGroup (); @@ -503,33 +551,30 @@ warning ("WITH DEBUGGING"); use_markup = true, xalign = 0.5f }; - title_label.get_style_context ().add_class (Granite.STYLE_CLASS_H3_LABEL); + title_label.add_css_class (Granite.STYLE_CLASS_H3_LABEL); title_label.show (); grade_label = new Gtk.Label ("Easy") { use_markup = true, xalign = 0.5f }; - grade_label.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL); + grade_label.add_css_class (Granite.STYLE_CLASS_H4_LABEL); - var title_grid = new Gtk.Grid () { - orientation = Gtk.Orientation.VERTICAL - }; - title_grid.add (title_label); - title_grid.add (grade_label); - title_grid.show_all (); + var title_grid = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); + title_grid.append (title_label); + title_grid.append (grade_label); + // title_grid.show_all (); progress_stack = new Gtk.Stack (); progress_stack.add_named (progress_indicator, "Progress"); progress_stack.add_named (title_grid, "Title"); progress_stack.set_visible_child_name ("Title"); - header_bar = new Hdy.HeaderBar () { - has_subtitle = false, - show_close_button = true, - custom_title = progress_stack + header_bar = new Gtk.HeaderBar () { + show_title_buttons = true, + title_widget = progress_stack }; - header_bar.get_style_context ().add_class ("gnonograms-header"); + header_bar.add_css_class ("gnonograms-header"); header_bar.pack_start (load_game_button); header_bar.pack_start (save_game_button); header_bar.pack_start (save_game_as_button); @@ -543,43 +588,44 @@ warning ("WITH DEBUGGING"); header_bar.pack_end (auto_solve_button); header_bar.pack_end (hint_button); - toast = new Granite.Widgets.Toast ("") { - halign = Gtk.Align.START, - valign = Gtk.Align.START - }; - toast.set_default_action (null); + // toast = new Adw.Toast (""); + // toast.set_default_action (null); - row_clue_box = new LabelBox (Gtk.Orientation.VERTICAL, this); - column_clue_box = new LabelBox (Gtk.Orientation.HORIZONTAL, this); + row_clue_box = new ClueBox (Gtk.Orientation.VERTICAL, this); + column_clue_box = new ClueBox (Gtk.Orientation.HORIZONTAL, this); cell_grid = new CellGrid (this); main_grid = new Gtk.Grid () { row_spacing = 0, - column_spacing = GRID_COLUMN_SPACING, - border_width = GRID_BORDER, - expand = true + column_spacing = GRID_COLUMN_SPACING + // border_width = GRID_BORDER, + // expand = true }; main_grid.attach (row_clue_box, 0, 1, 1, 1); /* Clues fordimensions.height*/ main_grid.attach (column_clue_box, 1, 0, 1, 1); /* Clues for columns */ main_grid.attach (cell_grid, 1, 1, 1, 1); - var ev = new Gtk.EventBox () {expand = true}; - ev.add_events (Gdk.EventMask.SCROLL_MASK); - ev.scroll_event.connect (on_grid_scroll_event); - ev.add (main_grid); + var scroll_controller = new Gtk.EventControllerScroll (Gtk.EventControllerScrollFlags.VERTICAL); + main_grid.add_controller (scroll_controller); + scroll_controller.scroll.connect (on_grid_scroll); + key_controller = new Gtk.EventControllerKey (); + main_grid.add_controller (key_controller); + key_controller.key_released.connect ((keyval, keycode, state) => { + if (keyval == drawing_with_key) { + stop_painting (); + } - overlay = new Gtk.Overlay () { - expand = true - }; - overlay.add_overlay (toast); - overlay.add (ev); + return; + }); - var grid = new Gtk.Grid () { - orientation = Gtk.Orientation.VERTICAL + toast_overlay = new Adw.ToastOverlay () { + child = main_grid }; - grid.add (header_bar); - grid.add (overlay); - add (grid); + + var grid = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); + grid.append (header_bar); + grid.append (toast_overlay); + child = grid; var flags = BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE; bind_property ("restart-destructive", restart_button, "restart-destructive", BindingFlags.SYNC_CREATE); @@ -597,7 +643,7 @@ warning ("WITH DEBUGGING"); // Avoid updating header bar while generating otherwise generation will be cancelled. // Headerbar will update when generation finished. - if (controller.game_state != GameState.GENERATING) { + if (controller.game_state != GameState.GENERATING) { update_header_bar (); } }); @@ -646,15 +692,15 @@ warning ("WITH DEBUGGING"); height = 768 }; - Gdk.Window? window = get_window (); - if (window != null) { - monitor_area = Utils.get_monitor_area (screen, window); + Gdk.Surface? surface = get_surface (); + if (surface != null) { + monitor_area = Utils.get_monitor_area (surface); } var available_screen_width = monitor_area.width * 0.9 - 2 * GRID_BORDER - GRID_COLUMN_SPACING; var max_cell_width = available_screen_width / (controller.dimensions.width * (1.0 + GRID_LABELBOX_RATIO)); - var available_grid_height = (int)(window.get_height () - header_bar.get_allocated_height () - 2 * GRID_BORDER); + var available_grid_height = (int)(surface.get_height () - header_bar.get_allocated_height () - 2 * GRID_BORDER); var opt_cell_height = (int)(available_grid_height / (controller.dimensions.height * (1.0 + GRID_LABELBOX_RATIO))); var available_screen_height = monitor_area.height * 0.9 - header_bar.get_allocated_height () - 2 * GRID_BORDER; @@ -669,43 +715,35 @@ warning ("WITH DEBUGGING"); }); - cell_grid.leave_notify_event.connect (() => { + cell_grid.leave.connect (() => { row_clue_box.unhighlight_all (); column_clue_box.unhighlight_all (); - return false; }); - cell_grid.button_press_event.connect ((event) => { - if (event.type == Gdk.EventType.@2BUTTON_PRESS || event.button == Gdk.BUTTON_MIDDLE) { + cell_grid.button_pressed.connect ((button, double_click) => { + if (double_click || button == Gdk.BUTTON_MIDDLE) { drawing_with_state = controller.game_state == GameState.SOLVING ? CellState.UNKNOWN : CellState.EMPTY; } else { - drawing_with_state = event.button == Gdk.BUTTON_PRIMARY ? CellState.FILLED : CellState.EMPTY; + drawing_with_state = button == Gdk.BUTTON_PRIMARY ? CellState.FILLED : CellState.EMPTY; } make_move_at_cell (); - return true; }); - key_release_event.connect ((event) => { - if (event.keyval == drawing_with_key) { - stop_painting (); - } - return false; - }); - cell_grid.button_release_event.connect (stop_painting); + cell_grid.button_released.connect (stop_painting); // Force window to follow grid size in both native and flatpak installs - cell_grid.size_allocate.connect ((alloc) => { - Idle.add (() => { - var width = alloc.width * (1 + GRID_LABELBOX_RATIO); - var height = alloc.height * (1 + GRID_LABELBOX_RATIO); - resize ((int)width, (int)height); - return Source.REMOVE; - }); - }); - - show_all (); + // cell_grid.size_allocate.connect ((alloc) => { + // Idle.add (() => { + // var width = alloc.width * (1 + GRID_LABELBOX_RATIO); + // var height = alloc.height * (1 + GRID_LABELBOX_RATIO); + // resize ((int)width, (int)height); + // return Source.REMOVE; + // }); + // }); + + // show_all (); } public string[] get_clues (bool is_column) { @@ -713,22 +751,22 @@ warning ("WITH DEBUGGING"); return label_box.get_clues (); } - public void update_labels_from_string_array (string[] clues, bool is_column) { + public void update_clues_from_string_array (string[] clues, bool is_column) { var clue_box = is_column ? column_clue_box : row_clue_box; var lim = is_column ? controller.dimensions.width : controller.dimensions.height; for (int i = 0; i < lim; i++) { - clue_box.update_label_text (i, clues[i]); + clue_box.update_clue_text (i, clues[i]); } } - public void update_labels_from_solution () { + public void update_clues_from_solution () { for (int r = 0; r < controller.dimensions.height; r++) { - row_clue_box.update_label_text (r, model.get_label_text_from_solution (r, false)); + row_clue_box.update_clue_text (r, model.get_label_text_from_solution (r, false)); } for (int c = 0; c < controller.dimensions.width; c++) { - column_clue_box.update_label_text (c, model.get_label_text_from_solution (c, true)); + column_clue_box.update_clue_text (c, model.get_label_text_from_solution (c, true)); } update_all_labels_completeness (); @@ -741,8 +779,7 @@ warning ("WITH DEBUGGING"); } public void send_notification (string text) { - toast.title = text.dup (); - toast.send_notification (); + toast_overlay.add_toast (new Adw.Toast (text)); } public void show_working (Cancellable cancellable, string text = "") { @@ -813,21 +850,21 @@ warning ("WITH DEBUGGING"); private void update_all_labels_completeness () { for (int r = 0; r < controller.dimensions.height; r++) { - update_label_complete (r, false); + update_clue_complete (r, false); } for (int c = 0; c < controller.dimensions.width; c++) { - update_label_complete (c, true); + update_clue_complete (c, true); } } - private void update_label_complete (uint idx, bool is_col) { + private void update_clue_complete (uint idx, bool is_col) { var lbox = is_col ? column_clue_box : row_clue_box; if (controller.game_state == GameState.SOLVING && strikeout_complete) { var blocks = Gee.List.empty (); blocks = model.get_complete_blocks_from_working (idx, is_col); - lbox.update_label_complete (idx, blocks); + lbox.update_clue_complete (idx, blocks); } else { lbox.clear_formatting (idx); } @@ -857,11 +894,11 @@ warning ("WITH DEBUGGING"); var col = current_cell.col; if (controller.game_state == GameState.SETTING) { - row_clue_box.update_label_text (row, model.get_label_text_from_solution (row, false)); - column_clue_box.update_label_text (col, model.get_label_text_from_solution (col, true)); + row_clue_box.update_clue_text (row, model.get_label_text_from_solution (row, false)); + column_clue_box.update_clue_text (col, model.get_label_text_from_solution (col, true)); } else { - update_label_complete (row, false); - update_label_complete (col, true); + update_clue_complete (row, false); + update_clue_complete (col, true); } return cell; @@ -882,32 +919,22 @@ warning ("WITH DEBUGGING"); }); } - private bool stop_painting () { + private void stop_painting () { drawing_with_state = CellState.UNDEFINED; drawing_with_key = 0; - return false; } /** With Control pressed, zoom using the fontsize. **/ - private bool on_grid_scroll_event (Gdk.EventScroll event) { - if (Gdk.ModifierType.CONTROL_MASK in event.state) { - switch (event.direction) { - case Gdk.ScrollDirection.UP: - change_cell_size (false); - break; - - case Gdk.ScrollDirection.DOWN: - change_cell_size (true); - break; - - default: - break; - } - - return true; + private bool on_grid_scroll (double dx, double dy) { + uint keyval; + Gdk.ModifierType modifiers; + ((Gdk.KeyEvent)key_controller.get_current_event ()).get_match (out keyval, out modifiers); + if (Gdk.ModifierType.CONTROL_MASK in modifiers) { + change_cell_size (dy > 0); + return Gdk.EVENT_STOP; } - return false; + return Gdk.EVENT_PROPAGATE; } /** Action callbacks **/ @@ -1036,10 +1063,8 @@ warning ("WITH DEBUGGING"); } drawing_with_state = cs; - var current_event = Gtk.get_current_event (); - if (current_event.type == Gdk.EventType.KEY_PRESS) { - drawing_with_key = ((Gdk.EventKey)current_event).keyval; - } + var current_event = key_controller.get_current_event (); + drawing_with_key = ((Gdk.KeyEvent)current_event).get_keyval (); make_move_at_cell (); } From a878884c7a2a842bec652c226e8b0aa98f6c4f50 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 11 Jul 2022 19:52:42 +0100 Subject: [PATCH 002/142] Get to run native * Subclass Gtk.Widget not final classes * Comment out problematic settings code * Use headerbar properly * Use icon-name property in buttons --- libcore/widgets/Clue.vala | 39 ++++++++++++++++++---------- src/Application.vala | 13 +++++----- src/Controller.vala | 2 +- src/View.vala | 54 ++++++++++++--------------------------- 4 files changed, 50 insertions(+), 58 deletions(-) diff --git a/libcore/widgets/Clue.vala b/libcore/widgets/Clue.vala index 824b6ce..cd4b4dd 100644 --- a/libcore/widgets/Clue.vala +++ b/libcore/widgets/Clue.vala @@ -68,14 +68,22 @@ class Gnonograms.Clue : Gtk.Widget { ); } + static construct { + set_layout_manager_type (typeof (Gtk.BinLayout)); + } + construct { - label = new Gtk.Label ("0") { + label = new Gtk.Label ("") { xalign = _vertical_text ? (float)0.5 : (float)1.0, yalign = vertical_text ? (float)1.0 : (float)0.5, has_tooltip = true, use_markup = true }; - + + label.set_parent (this); + + text = "0"; + realize.connect_after (() => { update_markup (); }); @@ -83,16 +91,15 @@ class Gnonograms.Clue : Gtk.Widget { public void highlight (bool is_highlight) { if (is_highlight) { - get_style_context ().add_class (Granite.STYLE_CLASS_ACCENT); + label.add_css_class (Granite.STYLE_CLASS_ACCENT); } else { - get_style_context ().remove_class (Granite.STYLE_CLASS_ACCENT); + label.remove_css_class (Granite.STYLE_CLASS_ACCENT); } } public void clear_formatting () { - var sc = get_style_context (); - sc.remove_class ("warn"); - sc.remove_class ("dim"); + label.remove_css_class ("warn"); + label.remove_css_class ("dim"); } public void update_complete (Gee.List _grid_blocks) { @@ -102,9 +109,8 @@ class Gnonograms.Clue : Gtk.Widget { block.is_error = false; } - var sc = get_style_context (); - sc.remove_class ("warn"); - sc.remove_class ("dim"); + label.remove_css_class ("warn"); + label.remove_css_class ("dim"); uint complete = 0; uint errors = 0; @@ -150,12 +156,12 @@ class Gnonograms.Clue : Gtk.Widget { } if (errors > 0) { - sc.add_class ("warn"); + label.add_css_class ("warn"); } if (complete == clue_blocks.size && errors == 0 && grid_null == 0) { update_markup (); - sc.add_class ("dim"); + label.add_css_class ("dim"); return; } @@ -220,18 +226,25 @@ class Gnonograms.Clue : Gtk.Widget { } if (errors > 0) { - sc.add_class ("warn"); + label.add_css_class ("warn"); } update_markup (); } private void update_markup () { +// if (!(label is Gtk.Label)) { +// warning ("invalid label"); +// return; +// } label.set_markup ("".printf (_fontsize) + get_markup () + ""); update_tooltip (); } private void update_tooltip () { + // if (!(label is Gtk.Label)) { + // return; + // } label.set_tooltip_markup ("".printf (_fontsize) + _("Freedom = %u").printf (n_cells - Utils.blockextent_from_clue (_text)) + "" diff --git a/src/Application.vala b/src/Application.vala index e97245a..15a723a 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -43,14 +43,14 @@ public class Gnonograms.App : Gtk.Application { add_action (quit_action); set_accels_for_action ("app.quit", {"q"}); - var granite_settings = Granite.Settings.get_default (); - var gtk_settings = Gtk.Settings.get_default (); + // var granite_settings = Granite.Settings.get_default (); + // var gtk_settings = Gtk.Settings.get_default (); - gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; + // gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; - granite_settings.notify["prefers-color-scheme"].connect (() => { - gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; - }); + // granite_settings.notify["prefers-color-scheme"].connect (() => { + // gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; + // }); } public override void open (File[] files, string hint) { @@ -85,7 +85,6 @@ public static int main (string[] args) { var opt_context = new OptionContext (N_("[Gnonogram Puzzle File (.gno)]")); opt_context.set_translation_domain (Config.APP_ID); opt_context.add_main_entries (OPTIONS, Config.APP_ID); - // opt_context.add_group (Gtk.get_option_group (true)); opt_context.parse (ref args); } catch (OptionError e) { printerr ("error: %s\n", e.message); diff --git a/src/Controller.vala b/src/Controller.vala index a1327ae..03e8b5f 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -235,7 +235,7 @@ public class Gnonograms.Controller : GLib.Object { // window.move (x, y); } else { /* Error normally thrown running uninstalled */ - critical ("Unable to restore settings - using defaults"); /* Maybe running uninstalled */ + warning ("Unable to restore settings - using defaults"); /* Maybe running uninstalled */ /* Default puzzle parameters */ game_state = GameState.SOLVING; generator_grade = Difficulty.MODERATE; diff --git a/src/View.vala b/src/View.vala index a0cbdc8..e6d9ce4 100644 --- a/src/View.vala +++ b/src/View.vala @@ -88,9 +88,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } private class HeaderButton : Gtk.Button { - public Gtk.Image image { get; construct; } public HeaderButton (string icon_name, string action_name, string text) { Object ( + icon_name: icon_name, action_name: action_name, tooltip_markup: Granite.markup_accel_tooltip ( View.app.get_accels_for_action (action_name), text @@ -100,12 +100,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { construct { valign = Gtk.Align.CENTER; - - image = new Gtk.Image.from_icon_name (icon_name) { - icon_size = Gtk.IconSize.LARGE - }; - - child = image; } } @@ -118,11 +112,11 @@ public class Gnonograms.View : Gtk.ApplicationWindow { notify["restart-destructive"].connect (() => { if (restart_destructive) { - image.add_css_class ("warn"); - image.remove_css_class ("dim"); + add_css_class ("warn"); + remove_css_class ("dim"); } else { - image.remove_css_class ("warn"); - image.add_css_class ("dim"); + remove_css_class ("warn"); + add_css_class ("dim"); } }); @@ -146,7 +140,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { combo_box_text.active_id = ((uint)value).clamp (MIN_GRADE, Difficulty.MAXIMUM).to_string (); } } - + static construct { set_layout_manager_type (typeof (Gtk.BinLayout)); } @@ -154,7 +148,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { construct { combo_box_text = new Gtk.ComboBoxText (); combo_box_text.set_parent (this); - + foreach (Difficulty d in Difficulty.all_human ()) { combo_box_text.append (((uint)d).to_string (), d.to_string ()); } @@ -163,30 +157,17 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private class DimensionSpinButton : Gtk.Widget { // SpinButton public Gtk.SpinButton spin_button { get; construct; } - // public DimensionSpinButton () { - // Object ( - // adjustment: new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), - // climb_rate: 5.0, - // digits: 0, - // snap_to_ticks: true, - // orientation: Gtk.Orientation.HORIZONTAL, - // margin_top: 3, - // margin_bottom: 3, - // width_chars: 3, - // can_focus: true - // ); - // } - + public uint size { get { return (uint)(spin_button.@value); } - + set { spin_button.@value = value; } } - + static construct { set_layout_manager_type (typeof (Gtk.BinLayout)); } @@ -204,7 +185,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { width_chars = 3, can_focus = true }; - + spin_button.set_parent (this); } } @@ -214,7 +195,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public AppMenu (Controller controller) { Object ( - // tooltip_text: _("Options"), controller: controller ); } @@ -225,11 +205,12 @@ public class Gnonograms.View : Gtk.ApplicationWindow { construct { menu_button = new Gtk.MenuButton () { - tooltip_text = _("Options") + tooltip_text = _("Options"), + icon_name = "open-menu" }; - + menu_button.set_parent (this); - + var zoom_out_button = new Gtk.Button.from_icon_name ("zoom-out-symbolic") { action_name = ACTION_PREFIX + ACTION_ZOOM_OUT, tooltip_markup = Granite.markup_accel_tooltip ( @@ -292,7 +273,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { }); menu_button.set_popover (app_popover); - menu_button.child = new Gtk.Image.from_icon_name ("open-menu") { icon_size = Gtk.IconSize.LARGE }; + // menu_button.child = new Gtk.Image.from_icon_name ("open-menu") { icon_size = Gtk.IconSize.LARGE }; app_popover.apply_settings.connect (() => { controller.generator_grade = grade_setting.grade; @@ -588,8 +569,7 @@ warning ("WITH DEBUGGING"); header_bar.pack_end (auto_solve_button); header_bar.pack_end (hint_button); - // toast = new Adw.Toast (""); - // toast.set_default_action (null); + set_titlebar (header_bar); row_clue_box = new ClueBox (Gtk.Orientation.VERTICAL, this); column_clue_box = new ClueBox (Gtk.Orientation.HORIZONTAL, this); From be46a0789cabdd39dd869be0f92de79823405d79 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 12 Jul 2022 10:55:32 +0100 Subject: [PATCH 003/142] Split out some private classes --- meson.build | 4 + src/Controller.vala | 4 +- src/HeaderBar/AppPopover.vala | 156 ++++++++++++ src/HeaderBar/HeaderButton.vala | 35 +++ src/HeaderBar/ProgressIndicator.vala | 83 +++++++ src/HeaderBar/RestartButton.vala | 42 ++++ src/View.vala | 340 +++------------------------ 7 files changed, 352 insertions(+), 312 deletions(-) create mode 100644 src/HeaderBar/AppPopover.vala create mode 100644 src/HeaderBar/HeaderButton.vala create mode 100644 src/HeaderBar/ProgressIndicator.vala create mode 100644 src/HeaderBar/RestartButton.vala diff --git a/meson.build b/meson.build index 3faf5ea..9dfce43 100644 --- a/meson.build +++ b/meson.build @@ -34,6 +34,10 @@ executable ( 'src/Application.vala', 'src/Controller.vala', 'src/View.vala', + 'src/HeaderBar/HeaderButton.vala', + 'src/HeaderBar/ProgressIndicator.vala', + 'src/HeaderBar/RestartButton.vala', + 'src/HeaderBar/AppPopover.vala', 'src/services/RandomPatternGenerator.vala', 'src/services/RandomGameGenerator.vala', 'libcore/widgets/Cluebox.vala', diff --git a/src/Controller.vala b/src/Controller.vala index 03e8b5f..9c0bae0 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -216,7 +216,7 @@ public class Gnonograms.Controller : GLib.Object { /* Error normally thrown on first run */ debug ("Error deleting temporary game file %s - %s", temporary_game_path, e.message); } finally { - warning ("writing unsaved game to %s", temporary_game_path); + debug ("writing unsaved game to %s", temporary_game_path); /* Save solution and current state */ write_game (temporary_game_path, true); } @@ -229,10 +229,8 @@ public class Gnonograms.Controller : GLib.Object { if (saved_state != null) { int cell_size; saved_state.get ("cell-size", "i", out cell_size); - // saved_state.get ("window-position", "(ii)", out x, out y); current_game_path = saved_state.get_string ("current-game-path"); view.cell_size = cell_size; - // window.move (x, y); } else { /* Error normally thrown running uninstalled */ warning ("Unable to restore settings - using defaults"); /* Maybe running uninstalled */ diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala new file mode 100644 index 0000000..ac940f2 --- /dev/null +++ b/src/HeaderBar/AppPopover.vala @@ -0,0 +1,156 @@ +/* AppPopover.vala +* Copyright (C) 2010-2022 Jeremy Wootten +* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +* +* Author: Jeremy Wootten +*/ + public class Gnonograms.AppPopover : Gtk.Popover { + public signal void apply_settings (); + + private Gtk.ComboBoxText grade_setting; + private Gtk.SpinButton row_setting; + private Gtk.SpinButton column_setting; + private Gtk.Entry title_setting; + + public Difficulty grade { + get { + return (Difficulty)(int.parse (grade_setting.get_active_text ())); + } + + set { + grade_setting.active_id = ((uint)value).clamp (MIN_GRADE, Difficulty.MAXIMUM).to_string (); + } + } + + public uint rows { + get { + return (uint)(row_setting.@value); + } + + set { + row_setting.@value = value; + } + } + + public uint columns { + get { + return (uint)(column_setting.@value); + } + + set { + column_setting.@value = value; + } + } + + public string title { + get { + return title_setting.text; + } + + set { + title_setting.text = value; + } + } + + construct { + var app = (Gtk.Application)(Application.get_default ()); + var zoom_out_button = new Gtk.Button.from_icon_name ("zoom-out-symbolic") { + action_name = View.ACTION_PREFIX + View.ACTION_ZOOM_OUT, + tooltip_markup = Granite.markup_accel_tooltip ( + app.get_accels_for_action (View.ACTION_PREFIX + View.ACTION_ZOOM_OUT), _("Zoom out") + ) + }; + + var zoom_in_button = new Gtk.Button.from_icon_name ("zoom-in-symbolic") { + action_name = View.ACTION_PREFIX + View.ACTION_ZOOM_IN, + tooltip_markup = Granite.markup_accel_tooltip ( + app.get_accels_for_action (View.ACTION_PREFIX + View.ACTION_ZOOM_IN), _("Zoom in") + ) + }; + + var size_grid = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6) { + homogeneous = true, + hexpand = true, + }; + // size_grid.add_css_class (Gtk.STYLE_CLASS_LINKED); + size_grid.append (zoom_out_button); + size_grid.append (zoom_in_button); + + grade_setting = new Gtk.ComboBoxText (); + foreach (Difficulty d in Difficulty.all_human ()) { + grade_setting.append (((uint)d).to_string (), d.to_string ()); + } + + row_setting = new Gtk.SpinButton ( + new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), + 5.0, + 0 + ) { + snap_to_ticks = true, + orientation = Gtk.Orientation.HORIZONTAL, + width_chars = 3, + }; + + column_setting = new Gtk.SpinButton ( + new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), + 5.0, + 0 + ) { + snap_to_ticks = true, + orientation = Gtk.Orientation.HORIZONTAL, + width_chars = 3, + }; + + title_setting = new Gtk.Entry () { + placeholder_text = _("Enter title of game here") + }; + + var settings_grid = new Gtk.Grid () { + orientation = Gtk.Orientation.VERTICAL, + row_spacing = 6, + column_homogeneous = false + }; + settings_grid.attach (new Gtk.Label (_("Name:")), 0, 0, 1); + settings_grid.attach (title_setting, 1, 0, 3); + settings_grid.attach (new Gtk.Label (_("Difficulty:")), 0, 1, 1); + settings_grid.attach (grade_setting, 1, 1, 3); + settings_grid.attach (new Gtk.Label (_("Rows:")), 0, 2, 1); + settings_grid.attach (row_setting, 1, 2, 1); + settings_grid.attach (new Gtk.Label (_("Columns:")), 0, 3, 1); + settings_grid.attach (column_setting, 1, 3, 1); + + var cancel_button = new Gtk.Button.with_label (_("Cancel")); + var apply_button = new Gtk.Button.with_label (_("Apply")); + default_widget = apply_button; + cancel_button.clicked.connect (() => { + hide (); + }); + apply_button.clicked.connect (() => { + apply_settings (); + hide (); + }); + var popover_actions = new Gtk.ActionBar (); + popover_actions.pack_start (cancel_button); + popover_actions.pack_end (apply_button); + + var main_grid = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); + main_grid.append (size_grid); + main_grid.append (settings_grid); + main_grid.append (popover_actions); + + child = main_grid; + } + } + diff --git a/src/HeaderBar/HeaderButton.vala b/src/HeaderBar/HeaderButton.vala new file mode 100644 index 0000000..2cf330f --- /dev/null +++ b/src/HeaderBar/HeaderButton.vala @@ -0,0 +1,35 @@ +/* View.vala + * Copyright (C) 2010-2021 Jeremy Wootten + * + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + * + * Author: Jeremy Wootten + */ + + public class Gnonograms.HeaderButton : Gtk.Button { + public HeaderButton (string icon_name, string action_name, string text) { + Object ( + icon_name: icon_name, + action_name: action_name, + tooltip_markup: Granite.markup_accel_tooltip ( + View.app.get_accels_for_action (action_name), text + ) + ); + } + + construct { + valign = Gtk.Align.CENTER; + } + + } diff --git a/src/HeaderBar/ProgressIndicator.vala b/src/HeaderBar/ProgressIndicator.vala new file mode 100644 index 0000000..f667b7a --- /dev/null +++ b/src/HeaderBar/ProgressIndicator.vala @@ -0,0 +1,83 @@ +/* Copyright (C) 2010-2022 Jeremy Wootten + * + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + * + * Author: Jeremy Wootten + */ + + public class Gnonograms.ProgressIndicator : Gtk.Box { + private Gtk.Spinner spinner; + private Gtk.Button cancel_button; + private Gtk.Label label; + + public string text { + set { + label.label = value; + } + } + + public Cancellable? cancellable { get; set; } + + public ProgressIndicator () { + Object ( + orientation: Gtk.Orientation.HORIZONTAL, + homogeneous: false, + spacing: 6, + valign: Gtk.Align.CENTER + ); + } + + construct { + spinner = new Gtk.Spinner (); + label = new Gtk.Label (null); + label.add_css_class (Granite.STYLE_CLASS_H3_LABEL); + + append (label); + append (spinner); + + cancel_button = new Gtk.Button (); + var img = new Gtk.Image.from_icon_name ("process-stop-symbolic") { + icon_size = Gtk.IconSize.LARGE, + tooltip_text = _("Cancel solving") + }; + cancel_button.child = img; + cancel_button.add_css_class ("warn"); + img.add_css_class ("warn"); + + append (cancel_button); + + cancel_button.clicked.connect (() => { + if (cancellable != null) { + cancellable.cancel (); + } + }); + + realize.connect (() => { + if (cancellable != null) { + cancel_button.show (); + } + + spinner.start (); + }); + + unrealize.connect (() => { + if (cancellable != null) { + cancellable = null; + cancel_button.hide (); + } + + spinner.stop (); + }); + } + } diff --git a/src/HeaderBar/RestartButton.vala b/src/HeaderBar/RestartButton.vala new file mode 100644 index 0000000..ebdcbc0 --- /dev/null +++ b/src/HeaderBar/RestartButton.vala @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010-2022 Jeremy Wootten + * + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + * + * Author: Jeremy Wootten + */ + + private class Gnonograms.RestartButton : Gnonograms.HeaderButton { + public bool restart_destructive { get; set; } + + construct { + restart_destructive = false; + + notify["restart-destructive"].connect (() => { + if (restart_destructive) { + add_css_class ("warn"); + remove_css_class ("dim"); + } else { + remove_css_class ("warn"); + add_css_class ("dim"); + } + }); + + bind_property ("sensitive", this, "restart-destructive"); + } + + public RestartButton (string icon_name, string action_name, string text) { + base (icon_name, action_name, text); + } + } diff --git a/src/View.vala b/src/View.vala index e6d9ce4..6455016 100644 --- a/src/View.vala +++ b/src/View.vala @@ -1,5 +1,5 @@ /* View.vala - * Copyright (C) 2010-2021 Jeremy Wootten + * Copyright (C) 2010-2022 Jeremy Wootten * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,300 +18,6 @@ */ public class Gnonograms.View : Gtk.ApplicationWindow { - private class ProgressIndicator : Gtk.Box { - private Gtk.Spinner spinner; - private Gtk.Button cancel_button; - private Gtk.Label label; - - public string text { - set { - label.label = value; - } - } - - public Cancellable? cancellable { get; set; } - - public ProgressIndicator () { - Object ( - orientation: Gtk.Orientation.HORIZONTAL, - homogeneous: false, - spacing: 6, - valign: Gtk.Align.CENTER - ); - } - - construct { - spinner = new Gtk.Spinner (); - label = new Gtk.Label (null); - label.add_css_class (Granite.STYLE_CLASS_H3_LABEL); - - append (label); - append (spinner); - - cancel_button = new Gtk.Button (); - var img = new Gtk.Image.from_icon_name ("process-stop-symbolic") { - icon_size = Gtk.IconSize.LARGE, - tooltip_text = _("Cancel solving") - }; - cancel_button.child = img; - // cancel_button.no_show_all = true; - cancel_button.add_css_class ("warn"); - img.add_css_class ("warn"); - - append (cancel_button); - - // show_all (); - - cancel_button.clicked.connect (() => { - if (cancellable != null) { - cancellable.cancel (); - } - }); - - realize.connect (() => { - if (cancellable != null) { - cancel_button.show (); - } - - spinner.start (); - }); - - unrealize.connect (() => { - if (cancellable != null) { - cancellable = null; - cancel_button.hide (); - } - - spinner.stop (); - }); - } - } - - private class HeaderButton : Gtk.Button { - public HeaderButton (string icon_name, string action_name, string text) { - Object ( - icon_name: icon_name, - action_name: action_name, - tooltip_markup: Granite.markup_accel_tooltip ( - View.app.get_accels_for_action (action_name), text - ) - ); - } - - construct { - valign = Gtk.Align.CENTER; - } - - } - - private class RestartButton : HeaderButton { - public bool restart_destructive { get; set; } - - construct { - restart_destructive = false; - - notify["restart-destructive"].connect (() => { - if (restart_destructive) { - add_css_class ("warn"); - remove_css_class ("dim"); - } else { - remove_css_class ("warn"); - add_css_class ("dim"); - } - }); - - bind_property ("sensitive", this, "restart-destructive"); - } - - public RestartButton (string icon_name, string action_name, string text) { - base (icon_name, action_name, text); - } - } - - private class AppMenu : Gtk.Widget { // MenuButton - private class GradeChooser : Gtk.Widget { // ComboBoxText - public Gtk.ComboBoxText combo_box_text { get; construct; } - public Difficulty grade { - get { - return (Difficulty)(int.parse (combo_box_text.active_id)); - } - - set { - combo_box_text.active_id = ((uint)value).clamp (MIN_GRADE, Difficulty.MAXIMUM).to_string (); - } - } - - static construct { - set_layout_manager_type (typeof (Gtk.BinLayout)); - } - - construct { - combo_box_text = new Gtk.ComboBoxText (); - combo_box_text.set_parent (this); - - foreach (Difficulty d in Difficulty.all_human ()) { - combo_box_text.append (((uint)d).to_string (), d.to_string ()); - } - } - } - - private class DimensionSpinButton : Gtk.Widget { // SpinButton - public Gtk.SpinButton spin_button { get; construct; } - - public uint size { - get { - return (uint)(spin_button.@value); - } - - set { - spin_button.@value = value; - } - } - - static construct { - set_layout_manager_type (typeof (Gtk.BinLayout)); - } - - construct { - spin_button = new Gtk.SpinButton ( - new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), - 5.0, - 0 - ) { - snap_to_ticks = true, - orientation = Gtk.Orientation.HORIZONTAL, - margin_top = 3, - margin_bottom = 3, - width_chars = 3, - can_focus = true - }; - - spin_button.set_parent (this); - } - } - - public Gtk.MenuButton menu_button { get; construct; } - public unowned Controller controller { get; construct; } - - public AppMenu (Controller controller) { - Object ( - controller: controller - ); - } - - static construct { - set_layout_manager_type (typeof (Gtk.BinLayout)); - } - - construct { - menu_button = new Gtk.MenuButton () { - tooltip_text = _("Options"), - icon_name = "open-menu" - }; - - menu_button.set_parent (this); - - var zoom_out_button = new Gtk.Button.from_icon_name ("zoom-out-symbolic") { - action_name = ACTION_PREFIX + ACTION_ZOOM_OUT, - tooltip_markup = Granite.markup_accel_tooltip ( - app.get_accels_for_action (ACTION_PREFIX + ACTION_ZOOM_OUT), _("Zoom out") - ) - }; - - var zoom_in_button = new Gtk.Button.from_icon_name ("zoom-in-symbolic") { - action_name = ACTION_PREFIX + ACTION_ZOOM_IN, - tooltip_markup = Granite.markup_accel_tooltip ( - app.get_accels_for_action (ACTION_PREFIX + ACTION_ZOOM_IN), _("Zoom in") - ) - }; - - var size_grid = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6) { - homogeneous = true, - hexpand = true, - }; - // size_grid.add_css_class (Gtk.STYLE_CLASS_LINKED); - size_grid.append (zoom_out_button); - size_grid.append (zoom_in_button); - - var grade_setting = new GradeChooser (); - var row_setting = new DimensionSpinButton (); - var column_setting = new DimensionSpinButton (); - var title_setting = new Gtk.Entry () { - placeholder_text = _("Enter title of game here") - }; - - var settings_grid = new Gtk.Grid () { - orientation = Gtk.Orientation.VERTICAL, - row_spacing = 6, - column_homogeneous = false - }; - settings_grid.attach (new Gtk.Label (_("Name:")), 0, 0, 1); - settings_grid.attach (title_setting, 1, 0, 3); - settings_grid.attach (new Gtk.Label (_("Difficulty:")), 0, 1, 1); - settings_grid.attach (grade_setting, 1, 1, 3); - settings_grid.attach (new Gtk.Label (_("Rows:")), 0, 2, 1); - settings_grid.attach (row_setting, 1, 2, 1); - settings_grid.attach (new Gtk.Label (_("Columns:")), 0, 3, 1); - settings_grid.attach (column_setting, 1, 3, 1); - - var main_grid = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); - main_grid.append (size_grid); - main_grid.append (settings_grid); - - var app_popover = new AppPopover () { - child = main_grid - }; - - var popover_key_controller = new Gtk.EventControllerKey (); - main_grid.add_controller (popover_key_controller); - popover_key_controller.key_pressed.connect ((keyval, keycode, state) => { - app_popover.cancelled = (keyval == Gdk.Key.Escape); - - if (keyval == Gdk.Key.KP_Enter || keyval == Gdk.Key.Return) { - app_popover.hide (); - } - }); - - menu_button.set_popover (app_popover); - // menu_button.child = new Gtk.Image.from_icon_name ("open-menu") { icon_size = Gtk.IconSize.LARGE }; - - app_popover.apply_settings.connect (() => { - controller.generator_grade = grade_setting.grade; - controller.dimensions = {column_setting.size, row_setting.size}; - controller.game_name = title_setting.text; // Must come after changing dimensions - }); - - menu_button.activate.connect (() => { /* Allow parent to set values first */ - // if (active) { - grade_setting.grade = controller.generator_grade; - row_setting.size = controller.dimensions.height; - column_setting.size = controller.dimensions.width; - title_setting.text = controller.game_name; - // popover.show_all (); - // } - }); - } - - /** Popover that can be cancelled with Escape and closed by Enter **/ - private class AppPopover : Gtk.Popover { - public bool cancelled { get; set; } - public signal void apply_settings (); - public signal void cancel (); - - construct { - closed.connect (() => { - if (!cancelled) { - apply_settings (); - } else { - cancel (); - } - - cancelled = false; - }); - } - } - } - private const double USABLE_MONITOR_HEIGHT = 0.85; private const double USABLE_MONITOR_WIDTH = 0.95; private const int GRID_BORDER = 6; @@ -399,10 +105,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private ClueBox column_clue_box; private CellGrid cell_grid; private ProgressIndicator progress_indicator; - private AppMenu app_menu; + private Gtk.MenuButton menu_button; private CellState drawing_with_state = CellState.UNDEFINED; private Gtk.HeaderBar header_bar; - // private Adw.Toast toast; private Granite.ModeSwitch mode_switch; private Gtk.Grid main_grid; private Adw.ToastOverlay toast_overlay; @@ -480,8 +185,6 @@ warning ("WITH DEBUGGING"); } catch (Error e) { warning ("Error adding css provider: %s", e.message); } - - // Hdy.init (); } construct { @@ -492,7 +195,6 @@ warning ("WITH DEBUGGING"); view_actions.add_action_entries (view_action_entries, this); insert_action_group (ACTION_GROUP, view_actions); - foreach (var action in action_accelerators.get_keys ()) { var accels_array = action_accelerators[action].to_array (); accels_array += null; @@ -510,13 +212,34 @@ warning ("WITH DEBUGGING"); margin_end = 12, margin_start = 12, }; + hint_button = new HeaderButton ("help-contents", ACTION_PREFIX + ACTION_HINT, _("Suggest next move")); auto_solve_button = new HeaderButton ("system", ACTION_PREFIX + ACTION_SOLVE, _("Solve by Computer")); generate_button = new HeaderButton ("list-add", ACTION_PREFIX + ACTION_GENERATING_MODE, _("Generate New Puzzle")); - app_menu = new AppMenu (controller) { - tooltip_markup = Granite.markup_accel_tooltip (app.get_accels_for_action (ACTION_PREFIX + ACTION_OPTIONS), _("Options")) + + menu_button = new Gtk.MenuButton () { + tooltip_markup = Granite.markup_accel_tooltip ( + app.get_accels_for_action (ACTION_PREFIX + ACTION_OPTIONS), _("Options") + ), + icon_name = "open-menu" }; + var app_popover = new AppPopover (); + + menu_button.set_popover (app_popover); + app_popover.apply_settings.connect (() => { + controller.generator_grade = app_popover.grade; + controller.dimensions = {app_popover.columns, app_popover.rows}; + controller.game_name = app_popover.title; // Must come after changing dimensions + }); + + app_popover.show.connect (() => { /* Allow parent to set values first */ + app_popover.grade = controller.generator_grade; + app_popover.rows = controller.dimensions.height; + app_popover.columns = controller.dimensions.width; + app_popover.title = controller.game_name; + }); + // Unable to set markup on Granite.ModeSwitch so fake a Granite acellerator tooltip for now. mode_switch = new Granite.ModeSwitch.from_icon_name ("edit-symbolic", "head-thinking-symbolic") { margin_end = 12, @@ -544,7 +267,6 @@ warning ("WITH DEBUGGING"); var title_grid = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); title_grid.append (title_label); title_grid.append (grade_label); - // title_grid.show_all (); progress_stack = new Gtk.Stack (); progress_stack.add_named (progress_indicator, "Progress"); @@ -563,7 +285,7 @@ warning ("WITH DEBUGGING"); header_bar.pack_start (undo_button); header_bar.pack_start (redo_button); header_bar.pack_start (check_correct_button); - header_bar.pack_end (app_menu); + header_bar.pack_end (menu_button); header_bar.pack_end (generate_button); header_bar.pack_end (mode_switch); header_bar.pack_end (auto_solve_button); @@ -602,10 +324,10 @@ warning ("WITH DEBUGGING"); child = main_grid }; - var grid = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); - grid.append (header_bar); - grid.append (toast_overlay); - child = grid; + // var grid = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); + // grid.append (header_bar); + // grid.append (toast_overlay); + child = toast_overlay; var flags = BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE; bind_property ("restart-destructive", restart_button, "restart-destructive", BindingFlags.SYNC_CREATE); @@ -934,7 +656,7 @@ warning ("WITH DEBUGGING"); } private void action_options () { - app_menu.activate (); + menu_button.activate (); } #if WITH_DEBUGGING From 13fd8d2cf8bd29b5038fbabe2746d896b041b595 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 12 Jul 2022 11:06:57 +0100 Subject: [PATCH 004/142] Tweak margins in popover --- src/HeaderBar/AppPopover.vala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index ac940f2..73e0ef8 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -81,8 +81,9 @@ }; var size_grid = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6) { - homogeneous = true, - hexpand = true, + margin_top = margin_bottom = 12, + margin_start = margin_end = 12, + homogeneous = true }; // size_grid.add_css_class (Gtk.STYLE_CLASS_LINKED); size_grid.append (zoom_out_button); @@ -119,8 +120,10 @@ var settings_grid = new Gtk.Grid () { orientation = Gtk.Orientation.VERTICAL, - row_spacing = 6, - column_homogeneous = false + row_spacing = 12, + column_spacing = 12, + margin_start = margin_end = 12, + margin_bottom = 24 }; settings_grid.attach (new Gtk.Label (_("Name:")), 0, 0, 1); settings_grid.attach (title_setting, 1, 0, 3); From 30f174124f27ccd5b875205780e68d465ce187c8 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 12 Jul 2022 16:14:21 +0100 Subject: [PATCH 005/142] Fix Ctrl+scroll zooming; remove unneeded code --- libcore/widgets/Clue.vala | 7 ----- libcore/widgets/Cluebox.vala | 1 - src/HeaderBar/AppPopover.vala | 2 +- src/View.vala | 51 ++++++++++++++++++++++------------- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/libcore/widgets/Clue.vala b/libcore/widgets/Clue.vala index cd4b4dd..4382fcb 100644 --- a/libcore/widgets/Clue.vala +++ b/libcore/widgets/Clue.vala @@ -233,18 +233,11 @@ class Gnonograms.Clue : Gtk.Widget { } private void update_markup () { -// if (!(label is Gtk.Label)) { -// warning ("invalid label"); -// return; -// } label.set_markup ("".printf (_fontsize) + get_markup () + ""); update_tooltip (); } private void update_tooltip () { - // if (!(label is Gtk.Label)) { - // return; - // } label.set_tooltip_markup ("".printf (_fontsize) + _("Freedom = %u").printf (n_cells - Utils.blockextent_from_clue (_text)) + "" diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index d50f7b9..b7339a6 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -131,7 +131,6 @@ public class Gnonograms.ClueBox : Gtk.Box { } set_size (); - // show_all (); } private void set_size () { diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 73e0ef8..11486d7 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -85,7 +85,7 @@ margin_start = margin_end = 12, homogeneous = true }; - // size_grid.add_css_class (Gtk.STYLE_CLASS_LINKED); + size_grid.add_css_class ("linked"); size_grid.append (zoom_out_button); size_grid.append (zoom_in_button); diff --git a/src/View.vala b/src/View.vala index 6455016..c6c3c8b 100644 --- a/src/View.vala +++ b/src/View.vala @@ -224,9 +224,12 @@ warning ("WITH DEBUGGING"); icon_name = "open-menu" }; - var app_popover = new AppPopover (); + var app_popover = new AppPopover () { + has_arrow = false + }; menu_button.set_popover (app_popover); + app_popover.apply_settings.connect (() => { controller.generator_grade = app_popover.grade; controller.dimensions = {app_popover.columns, app_popover.rows}; @@ -307,11 +310,37 @@ warning ("WITH DEBUGGING"); main_grid.attach (column_clue_box, 1, 0, 1, 1); /* Clues for columns */ main_grid.attach (cell_grid, 1, 1, 1, 1); - var scroll_controller = new Gtk.EventControllerScroll (Gtk.EventControllerScrollFlags.VERTICAL); + var scroll_controller = new Gtk.EventControllerScroll ( + Gtk.EventControllerScrollFlags.VERTICAL | Gtk.EventControllerScrollFlags.DISCRETE + ); main_grid.add_controller (scroll_controller); - scroll_controller.scroll.connect (on_grid_scroll); + scroll_controller.scroll.connect ((dx, dy) => { + var modifiers = scroll_controller. + get_current_event_device (). + get_seat (). + get_keyboard (). + get_modifier_state (); + + if (modifiers == Gdk.ModifierType.CONTROL_MASK) { + Idle.add (() => { + if (dy > 0.0) { + action_zoom_in (); + } else { + action_zoom_out (); + } + + return Source.REMOVE; + }); + + return Gdk.EVENT_STOP; + } + + return Gdk.EVENT_PROPAGATE; + }); + key_controller = new Gtk.EventControllerKey (); main_grid.add_controller (key_controller); + key_controller.key_released.connect ((keyval, keycode, state) => { if (keyval == drawing_with_key) { stop_painting (); @@ -324,9 +353,6 @@ warning ("WITH DEBUGGING"); child = main_grid }; - // var grid = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); - // grid.append (header_bar); - // grid.append (toast_overlay); child = toast_overlay; var flags = BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE; @@ -626,19 +652,6 @@ warning ("WITH DEBUGGING"); drawing_with_key = 0; } - /** With Control pressed, zoom using the fontsize. **/ - private bool on_grid_scroll (double dx, double dy) { - uint keyval; - Gdk.ModifierType modifiers; - ((Gdk.KeyEvent)key_controller.get_current_event ()).get_match (out keyval, out modifiers); - if (Gdk.ModifierType.CONTROL_MASK in modifiers) { - change_cell_size (dy > 0); - return Gdk.EVENT_STOP; - } - - return Gdk.EVENT_PROPAGATE; - } - /** Action callbacks **/ private void action_restart () { controller.restart (); From 3da40622bd5e59f47d9df86359d0af7fe43aca87 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 12 Jul 2022 16:39:47 +0100 Subject: [PATCH 006/142] Remove resize buttons from popover (do not work properly in Gtk4) --- src/HeaderBar/AppPopover.vala | 36 ++++++----------------------------- 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 11486d7..a5e2245 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -65,30 +65,6 @@ } construct { - var app = (Gtk.Application)(Application.get_default ()); - var zoom_out_button = new Gtk.Button.from_icon_name ("zoom-out-symbolic") { - action_name = View.ACTION_PREFIX + View.ACTION_ZOOM_OUT, - tooltip_markup = Granite.markup_accel_tooltip ( - app.get_accels_for_action (View.ACTION_PREFIX + View.ACTION_ZOOM_OUT), _("Zoom out") - ) - }; - - var zoom_in_button = new Gtk.Button.from_icon_name ("zoom-in-symbolic") { - action_name = View.ACTION_PREFIX + View.ACTION_ZOOM_IN, - tooltip_markup = Granite.markup_accel_tooltip ( - app.get_accels_for_action (View.ACTION_PREFIX + View.ACTION_ZOOM_IN), _("Zoom in") - ) - }; - - var size_grid = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6) { - margin_top = margin_bottom = 12, - margin_start = margin_end = 12, - homogeneous = true - }; - size_grid.add_css_class ("linked"); - size_grid.append (zoom_out_button); - size_grid.append (zoom_in_button); - grade_setting = new Gtk.ComboBoxText (); foreach (Difficulty d in Difficulty.all_human ()) { grade_setting.append (((uint)d).to_string (), d.to_string ()); @@ -122,7 +98,7 @@ orientation = Gtk.Orientation.VERTICAL, row_spacing = 12, column_spacing = 12, - margin_start = margin_end = 12, + margin_start = margin_end = margin_top = 12, margin_bottom = 24 }; settings_grid.attach (new Gtk.Label (_("Name:")), 0, 0, 1); @@ -148,12 +124,12 @@ popover_actions.pack_start (cancel_button); popover_actions.pack_end (apply_button); - var main_grid = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); - main_grid.append (size_grid); - main_grid.append (settings_grid); - main_grid.append (popover_actions); + var main_widget = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); + // main_grid.append (size_grid); + main_widget.append (settings_grid); + main_widget.append (popover_actions); - child = main_grid; + child = main_widget; } } From c1f5de11a0e765a41f6396692d3dd2711f47d3a7 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 14 Jul 2022 10:08:08 +0100 Subject: [PATCH 007/142] Add missing flatpak permission --- com.github.jeremypw.gnonograms.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/com.github.jeremypw.gnonograms.yml b/com.github.jeremypw.gnonograms.yml index b9d677c..9929f1f 100644 --- a/com.github.jeremypw.gnonograms.yml +++ b/com.github.jeremypw.gnonograms.yml @@ -4,6 +4,7 @@ runtime-version: '7' sdk: io.elementary.Sdk command: com.github.jeremypw.gnonograms finish-args: + - '--device=dri' - '--share=ipc' - '--socket=wayland' - '--socket=fallback-x11' From 4db82a699c9aef5fdc1facb523daef9dc630745f Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 14 Jul 2022 10:09:50 +0100 Subject: [PATCH 008/142] Remove commented code; change signal names; add comments --- libcore/utils.vala | 2 -- libcore/widgets/Cellgrid.vala | 9 ++++----- libcore/widgets/Cluebox.vala | 5 ++--- src/Controller.vala | 1 - src/View.vala | 17 ++--------------- 5 files changed, 8 insertions(+), 26 deletions(-) diff --git a/libcore/utils.vala b/libcore/utils.vala index eeb879e..22a1503 100644 --- a/libcore/utils.vala +++ b/libcore/utils.vala @@ -280,7 +280,6 @@ namespace Gnonograms.Utils { dialog.set_default_response (Gtk.ResponseType.NO); } - // dialog.set_position (Gtk.WindowPosition.MOUSE); Gtk.ResponseType response = Gtk.ResponseType.NO; dialog.response.connect ((resp) => { dialog.destroy (); @@ -362,7 +361,6 @@ namespace Gnonograms.Utils { public Gdk.Rectangle get_monitor_area (Gdk.Surface surface) { var display = Gdk.Display.get_default (); var monitor = display.get_monitor_at_surface (surface); - // var monitor = display.get_monitor_at_window (window); return monitor.get_geometry (); } } diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index a94333d..e25801d 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -19,8 +19,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { public signal void cursor_moved (Cell from, Cell to); - public signal void button_pressed (uint button, bool double_click); - public signal void button_released (); + public signal void start_drawing (uint button, bool double_click); + public signal void stop_drawing (); public signal void leave (); public View view { get; construct; } @@ -104,13 +104,12 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { add_controller (button_controller); button_controller.pressed.connect ((n_press, x, y) => { var button_event = (Gdk.ButtonEvent)(button_controller.get_current_event ()); - button_pressed (button_event.get_button (), n_press > 1); + start_drawing (button_event.get_button (), n_press > 1); }); button_controller.released.connect ((n_press, x, y) => { - button_released (); + stop_drawing (); }); - // draw.connect (on_draw_event); set_draw_func (draw_func); notify["current-cell"].connect (() => { diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index b7339a6..ee9a57a 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -20,8 +20,8 @@ public class Gnonograms.ClueBox : Gtk.Box { public View view { get; construct; } - private uint n_clues = 0; - private uint n_cells = 0; + private uint n_clues = 0; // The number of cells this box spans. + private uint n_cells = 0; // The number of cells each clue addresses private List clues = null; public ClueBox (Gtk.Orientation _orientation, View view) { @@ -60,7 +60,6 @@ public class Gnonograms.ClueBox : Gtk.Box { } private Gnonograms.Clue? get_clue (uint index) { - // var n_children = get_children ().length (); if (index >= n_clues) { return null; } else { diff --git a/src/Controller.vala b/src/Controller.vala index 9c0bae0..f31775e 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -117,7 +117,6 @@ public class Gnonograms.Controller : GLib.Object { settings.bind ("clue-help", view, "strikeout-complete", SettingsBindFlags.DEFAULT); } - // view.show_all (); view.present (); restore_settings (); diff --git a/src/View.vala b/src/View.vala index c6c3c8b..256de06 100644 --- a/src/View.vala +++ b/src/View.vala @@ -448,7 +448,7 @@ warning ("WITH DEBUGGING"); column_clue_box.unhighlight_all (); }); - cell_grid.button_pressed.connect ((button, double_click) => { + cell_grid.start_drawing.connect ((button, double_click) => { if (double_click || button == Gdk.BUTTON_MIDDLE) { drawing_with_state = controller.game_state == GameState.SOLVING ? CellState.UNKNOWN : CellState.EMPTY; } else { @@ -458,20 +458,7 @@ warning ("WITH DEBUGGING"); make_move_at_cell (); }); - - - cell_grid.button_released.connect (stop_painting); - // Force window to follow grid size in both native and flatpak installs - // cell_grid.size_allocate.connect ((alloc) => { - // Idle.add (() => { - // var width = alloc.width * (1 + GRID_LABELBOX_RATIO); - // var height = alloc.height * (1 + GRID_LABELBOX_RATIO); - // resize ((int)width, (int)height); - // return Source.REMOVE; - // }); - // }); - - // show_all (); + cell_grid.stop_drawing.connect (stop_painting); } public string[] get_clues (bool is_column) { From f4794749b6d24bfcef9a656c2855ff3ca63c309b Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 14 Jul 2022 10:21:06 +0100 Subject: [PATCH 009/142] Lose unnecessary function in Controller --- src/Controller.vala | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index f31775e..3a508e8 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -355,7 +355,7 @@ public class Gnonograms.Controller : GLib.Object { history.from_string (reader.moves); if (history.can_go_back) { - make_move (history.get_current_move ()); + view.make_move (history.get_current_move ()); } } else { view.send_notification (_("Unable to load game. %s").printf (reader.err_msg)); @@ -447,10 +447,6 @@ public class Gnonograms.Controller : GLib.Object { return errors; } - private void make_move (Move mv) { - view.make_move (mv); - } - private void clear_history () { history.clear_all (); } @@ -464,7 +460,7 @@ public class Gnonograms.Controller : GLib.Object { var moves = solver.hint (row_clues, col_clues, model.copy_working_data ()); foreach (Move mv in moves) { - make_move (mv); + view.make_move (mv); history.record_move (mv.cell, mv.previous_state); } @@ -492,7 +488,7 @@ public class Gnonograms.Controller : GLib.Object { public bool next_move () { if (history.can_go_forward) { - make_move (history.pop_next_move ()); + view.make_move (history.pop_next_move ()); return true; } else { return false; @@ -501,7 +497,7 @@ public class Gnonograms.Controller : GLib.Object { public bool previous_move () { if (history.can_go_back) { - make_move (history.pop_previous_move ()); + view.make_move (history.pop_previous_move ()); return true; } else { return false; @@ -596,7 +592,7 @@ public class Gnonograms.Controller : GLib.Object { var moves = solver.debug (idx, is_column, row_clues, col_clues, model.copy_working_data ()); foreach (Move mv in moves) { - make_move (mv); + view.make_move (mv); history.record_move (mv.cell, mv.previous_state); } } From 7462405d25b3a2af73d6fe415fec89876d4b9e36 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 14 Jul 2022 18:16:50 +0100 Subject: [PATCH 010/142] Simplify * Clue, derive from Object, monitor ClueBox properties * ClueBox, children now Gtk.Labels * ClueBox, Use Gee.ArrayList to track children * Avoid size_request --- libcore/utils.vala | 2 +- libcore/widgets/Clue.vala | 58 ++++++---------- libcore/widgets/Cluebox.vala | 129 +++++++++++++++-------------------- src/View.vala | 23 +++++-- 4 files changed, 91 insertions(+), 121 deletions(-) diff --git a/libcore/utils.vala b/libcore/utils.vala index 22a1503..26c55df 100644 --- a/libcore/utils.vala +++ b/libcore/utils.vala @@ -240,7 +240,7 @@ namespace Gnonograms.Utils { return sb.str; } - public static int show_dlg (string primary_text, + private static int show_dlg (string primary_text, Gtk.MessageType type, string? secondary_text, Gtk.Window? parent) { diff --git a/libcore/widgets/Clue.vala b/libcore/widgets/Clue.vala index 4382fcb..f0c5f6e 100644 --- a/libcore/widgets/Clue.vala +++ b/libcore/widgets/Clue.vala @@ -17,32 +17,10 @@ * Author: Jeremy Wootten */ -class Gnonograms.Clue : Gtk.Widget { +class Gnonograms.Clue : Object { public Gtk.Label label { get; construct; } - private uint _n_cells; - public uint n_cells { - set { - _n_cells = value; - update_tooltip (); - } - - private get { - return _n_cells; - } - } - + public ClueBox cluebox {get; construct; } private int _fontsize; - private int _cell_size; - public int cell_size { - get { - return _cell_size; - } - set { - _cell_size = value; - _fontsize = (int)((double)value * 0.4); - update_markup (); - } - } private string _text; /* text of clue in horizontal form */ public string text { @@ -59,19 +37,15 @@ class Gnonograms.Clue : Gtk.Widget { public bool vertical_text { get; construct; } - private Gee.List clue_blocks; - private Gee.List grid_blocks; + private Gee.List clue_blocks; // List of blocks based on clue - public Clue (bool _vertical_text) { + public Clue (bool _vertical_text, ClueBox cluebox) { Object ( - vertical_text: _vertical_text + vertical_text: _vertical_text, + cluebox: cluebox ); } - static construct { - set_layout_manager_type (typeof (Gtk.BinLayout)); - } - construct { label = new Gtk.Label ("") { xalign = _vertical_text ? (float)0.5 : (float)1.0, @@ -80,11 +54,20 @@ class Gnonograms.Clue : Gtk.Widget { use_markup = true }; - label.set_parent (this); - text = "0"; - realize.connect_after (() => { + label.realize.connect_after (() => { + _fontsize = (int)((double)cluebox.cell_size * 0.4); + + update_markup (); + }); + + cluebox.notify["n_cells"].connect (() => { + update_tooltip (); + }); + + cluebox.notify["cell-size"].connect (() => { + _fontsize = (int)((double)cluebox.cell_size * 0.4); update_markup (); }); } @@ -102,8 +85,7 @@ class Gnonograms.Clue : Gtk.Widget { label.remove_css_class ("dim"); } - public void update_complete (Gee.List _grid_blocks) { - grid_blocks = _grid_blocks; + public void update_complete (Gee.List grid_blocks) { foreach (Block block in clue_blocks) { block.is_complete = false; block.is_error = false; @@ -239,7 +221,7 @@ class Gnonograms.Clue : Gtk.Widget { private void update_tooltip () { label.set_tooltip_markup ("".printf (_fontsize) + - _("Freedom = %u").printf (n_cells - Utils.blockextent_from_clue (_text)) + + _("Freedom = %u").printf (cluebox.n_cells - Utils.blockextent_from_clue (_text)) + "" ); } diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index ee9a57a..4189f8a 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -19,28 +19,24 @@ public class Gnonograms.ClueBox : Gtk.Box { public View view { get; construct; } - - private uint n_clues = 0; // The number of cells this box spans. - private uint n_cells = 0; // The number of cells each clue addresses - private List clues = null; + public uint n_cells { get; set; default = 0; } // The number of cells each clue addresses, monitored by clues + public uint cell_size { get; set; default = 0; } // Dimensions of individual grid cells, monitored by clues + private Gee.ArrayList clues; public ClueBox (Gtk.Orientation _orientation, View view) { Object ( view: view, homogeneous: true, spacing: 0, + hexpand: _orientation == Gtk.Orientation.HORIZONTAL ? false : true, + vexpand: _orientation == Gtk.Orientation.HORIZONTAL ? true : false, orientation: _orientation ); } construct { - view.notify["cell-size"].connect (() => { - clues.foreach ((clue) => { - clue.cell_size = view.cell_size; - }); - - set_size (); - }); + clues = new Gee.ArrayList (); + view.bind_property ("cell-size", this, "cell-size", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE); view.controller.notify ["dimensions"].connect (() => { var new_n_clues = orientation == Gtk.Orientation.HORIZONTAL ? @@ -51,98 +47,81 @@ public class Gnonograms.ClueBox : Gtk.Box { view.controller.dimensions.height : view.controller.dimensions.width; - if (new_n_clues != n_clues || new_n_cells != n_cells) { - n_clues = new_n_clues; + if (new_n_clues != clues.size) { + // n_cells = new_n_cells; + clues.@foreach ((clue) => { + remove (clue.label); + }); + + clues.clear (); + for (int index = 0; index < new_n_clues; index++) { + var clue = new Clue (orientation == Gtk.Orientation.HORIZONTAL, this); + clues.add (clue); + append (clue.label); + } + + return; + } + + // Do not need to recreate the clues if only n_cells changed + if (new_n_cells != n_cells) { n_cells = new_n_cells; - change_n_clues (); } + + // set_size (); }); } - private Gnonograms.Clue? get_clue (uint index) { - if (index >= n_clues) { - return null; - } else { - return clues.nth_data (n_clues - index - 1); + public string[] get_clue_texts () { + string[] clue_texts = new string [clues.size]; + foreach (var clue in clues) { + clue_texts += clue.text; } - } - public string[] get_clues () { - string[] clue_text = new string [n_clues]; - var index = n_clues; - clues.@foreach ((clue) => { // Delivers widgets in reverse order they were added - index--; - clue_text[index] = clue.text; - }); - - return clue_text; + return clue_texts; } public void highlight (uint index, bool is_highlight) { - var clue = get_clue (index); - if (clue != null) { - clue.highlight (is_highlight); + if (index < clues.size) { + clues[(int)index].highlight (is_highlight); } } public void unhighlight_all () { - clues.foreach ((clue) => { + foreach (var clue in clues) { clue.highlight (false); - }); + } } - public void update_clue_text (uint index, string? txt) { - var clue = get_clue (index); - if (clue != null) { - clue.text = txt ?? _(BLANKLABELTEXT); + public void update_clue_text (uint index, string? text) { + if (index < clues.size) { + clues[(int)index].text = text ?? _(BLANKLABELTEXT); } } public void clear_formatting (uint index) { - var clue = get_clue (index); - if (clue != null) { - clue.clear_formatting (); + if (index < clues.size) { + clues[(int)index].clear_formatting (); } } public void update_clue_complete (uint index, Gee.List grid_blocks) { - var clue = get_clue (index); - if (clue != null) { - clue.update_complete (grid_blocks); - } - } - - private void change_n_clues () { - clues.@foreach ((clue) => { - clue.destroy (); - }); - - clues = null; - - for (var i = 0; i < n_clues; i++) { - var clue = new Clue (orientation == Gtk.Orientation.HORIZONTAL) { - n_cells = this.n_cells, - cell_size = view.cell_size - }; - - append (clue); - clues.append (clue); + if (index < clues.size) { + clues[(int)index].update_complete (grid_blocks); } - - set_size (); } - private void set_size () { - int width = (int)(orientation == Gtk.Orientation.HORIZONTAL ? - n_clues * view.cell_size : - n_cells * view.cell_size * GRID_LABELBOX_RATIO - ); + // private void set_size () { + // int width = (int)(orientation == Gtk.Orientation.HORIZONTAL ? + // clues.size * view.cell_size : + // n_cells * view.cell_size * GRID_LABELBOX_RATIO + // ); - int height = (int)(orientation == Gtk.Orientation.HORIZONTAL ? - n_cells * view.cell_size * GRID_LABELBOX_RATIO : - n_clues * view.cell_size - ); + // int height = (int)(orientation == Gtk.Orientation.HORIZONTAL ? + // n_cells * view.cell_size * GRID_LABELBOX_RATIO : + // clues.size * view.cell_size + // ); - set_size_request (width, height); - } + // set_size_request (width, height); + // } } diff --git a/src/View.vala b/src/View.vala index 256de06..213f104 100644 --- a/src/View.vala +++ b/src/View.vala @@ -130,7 +130,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { Object ( model: _model, controller: controller, - resizable: false, + resizable: true, title: _("Gnonograms") ); } @@ -302,9 +302,9 @@ warning ("WITH DEBUGGING"); main_grid = new Gtk.Grid () { row_spacing = 0, - column_spacing = GRID_COLUMN_SPACING + column_spacing = GRID_COLUMN_SPACING, // border_width = GRID_BORDER, - // expand = true + // hexpand = true }; main_grid.attach (row_clue_box, 0, 1, 1, 1); /* Clues fordimensions.height*/ main_grid.attach (column_clue_box, 1, 0, 1, 1); /* Clues for columns */ @@ -313,6 +313,7 @@ warning ("WITH DEBUGGING"); var scroll_controller = new Gtk.EventControllerScroll ( Gtk.EventControllerScrollFlags.VERTICAL | Gtk.EventControllerScrollFlags.DISCRETE ); + main_grid.add_controller (scroll_controller); scroll_controller.scroll.connect ((dx, dy) => { var modifiers = scroll_controller. @@ -415,6 +416,9 @@ warning ("WITH DEBUGGING"); // Update cell-size if required to fit on screen but without changing window size unnecessarily // The dimensions may have increased or decreased so may need to increase or decrease cell size // It is assumed up to 90% of the screen area can be used + var n_cols = controller.dimensions.width; + var n_rows = controller.dimensions.height; + var monitor_area = Gdk.Rectangle () { width = 1024, height = 768 @@ -426,13 +430,13 @@ warning ("WITH DEBUGGING"); } var available_screen_width = monitor_area.width * 0.9 - 2 * GRID_BORDER - GRID_COLUMN_SPACING; - var max_cell_width = available_screen_width / (controller.dimensions.width * (1.0 + GRID_LABELBOX_RATIO)); + var max_cell_width = available_screen_width / (n_cols * (1.3)); var available_grid_height = (int)(surface.get_height () - header_bar.get_allocated_height () - 2 * GRID_BORDER); - var opt_cell_height = (int)(available_grid_height / (controller.dimensions.height * (1.0 + GRID_LABELBOX_RATIO))); + var opt_cell_height = (int)(available_grid_height / (n_rows * (1.3))); var available_screen_height = monitor_area.height * 0.9 - header_bar.get_allocated_height () - 2 * GRID_BORDER; - var max_cell_height = available_screen_height / (controller.dimensions.height * (1.0 + GRID_LABELBOX_RATIO)); + var max_cell_height = available_screen_height / (n_rows * (1.3)); var max_cell_size = (int)(double.min (max_cell_width, max_cell_height)); if (max_cell_size < cell_size) { @@ -441,6 +445,11 @@ warning ("WITH DEBUGGING"); cell_size = int.min (max_cell_size, opt_cell_height); } +warning ("setting window size"); + main_grid.set_size_request ( + (int)((double)(n_cols * cell_size) * 1.3), + (int)((double)(n_rows * cell_size) * 1.3) + ); }); cell_grid.leave.connect (() => { @@ -463,7 +472,7 @@ warning ("WITH DEBUGGING"); public string[] get_clues (bool is_column) { var label_box = is_column ? column_clue_box : row_clue_box; - return label_box.get_clues (); + return label_box.get_clue_texts (); } public void update_clues_from_string_array (string[] clues, bool is_column) { From 9ec0ad6e8372a6b3216c754417c199ce5733cc3c Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 14 Jul 2022 20:00:12 +0100 Subject: [PATCH 011/142] Set size of window and maingrid in View --- libcore/widgets/Cellgrid.vala | 4 +- libcore/widgets/Cluebox.vala | 28 ++-------- src/Controller.vala | 4 +- src/View.vala | 98 +++++++++++++++++++++-------------- 4 files changed, 69 insertions(+), 65 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index e25801d..33e8fb4 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -155,7 +155,9 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { cell_height = view.cell_size; /* Cause refresh of existing pattern */ highlight_pattern = new CellPattern.highlight (cell_width, cell_height); - set_size_request (cols * view.cell_size + (int)MINOR_GRID_LINE_WIDTH, rows * view.cell_size + (int)MINOR_GRID_LINE_WIDTH); + var width = cols * view.cell_size + (int)MINOR_GRID_LINE_WIDTH; + var height = rows * view.cell_size + (int)MINOR_GRID_LINE_WIDTH; + set_size_request (width, height); } private void draw_func (Gtk.DrawingArea drawing_area, Cairo.Context cr, int x, int y) { diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index 4189f8a..624e34c 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -37,7 +37,6 @@ public class Gnonograms.ClueBox : Gtk.Box { construct { clues = new Gee.ArrayList (); view.bind_property ("cell-size", this, "cell-size", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE); - view.controller.notify ["dimensions"].connect (() => { var new_n_clues = orientation == Gtk.Orientation.HORIZONTAL ? view.controller.dimensions.width : @@ -48,7 +47,7 @@ public class Gnonograms.ClueBox : Gtk.Box { view.controller.dimensions.width; if (new_n_clues != clues.size) { - // n_cells = new_n_cells; + n_cells = new_n_cells; clues.@foreach ((clue) => { remove (clue.label); }); @@ -59,16 +58,11 @@ public class Gnonograms.ClueBox : Gtk.Box { clues.add (clue); append (clue.label); } - - return; - } - - // Do not need to recreate the clues if only n_cells changed - if (new_n_cells != n_cells) { + } else if (new_n_cells != n_cells) { n_cells = new_n_cells; + } else { + return; } - - // set_size (); }); } @@ -110,18 +104,4 @@ public class Gnonograms.ClueBox : Gtk.Box { clues[(int)index].update_complete (grid_blocks); } } - - // private void set_size () { - // int width = (int)(orientation == Gtk.Orientation.HORIZONTAL ? - // clues.size * view.cell_size : - // n_cells * view.cell_size * GRID_LABELBOX_RATIO - // ); - - // int height = (int)(orientation == Gtk.Orientation.HORIZONTAL ? - // n_cells * view.cell_size * GRID_LABELBOX_RATIO : - // clues.size * view.cell_size - // ); - - // set_size_request (width, height); - // } } diff --git a/src/Controller.vala b/src/Controller.vala index 3a508e8..e9546be 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -229,7 +229,9 @@ public class Gnonograms.Controller : GLib.Object { int cell_size; saved_state.get ("cell-size", "i", out cell_size); current_game_path = saved_state.get_string ("current-game-path"); - view.cell_size = cell_size; + if (cell_size > 0) { + view.cell_size = cell_size; + } } else { /* Error normally thrown running uninstalled */ warning ("Unable to restore settings - using defaults"); /* Maybe running uninstalled */ diff --git a/src/View.vala b/src/View.vala index 213f104..69c8436 100644 --- a/src/View.vala +++ b/src/View.vala @@ -302,9 +302,8 @@ warning ("WITH DEBUGGING"); main_grid = new Gtk.Grid () { row_spacing = 0, - column_spacing = GRID_COLUMN_SPACING, - // border_width = GRID_BORDER, - // hexpand = true + margin_bottom = margin_end = GRID_BORDER, + column_spacing = GRID_COLUMN_SPACING }; main_grid.attach (row_clue_box, 0, 1, 1, 1); /* Clues fordimensions.height*/ main_grid.attach (column_clue_box, 1, 0, 1, 1); /* Clues for columns */ @@ -413,45 +412,15 @@ warning ("WITH DEBUGGING"); }); controller.notify["dimensions"].connect (() => { - // Update cell-size if required to fit on screen but without changing window size unnecessarily - // The dimensions may have increased or decreased so may need to increase or decrease cell size - // It is assumed up to 90% of the screen area can be used - var n_cols = controller.dimensions.width; - var n_rows = controller.dimensions.height; - - var monitor_area = Gdk.Rectangle () { - width = 1024, - height = 768 - }; - - Gdk.Surface? surface = get_surface (); - if (surface != null) { - monitor_area = Utils.get_monitor_area (surface); - } - - var available_screen_width = monitor_area.width * 0.9 - 2 * GRID_BORDER - GRID_COLUMN_SPACING; - var max_cell_width = available_screen_width / (n_cols * (1.3)); - - var available_grid_height = (int)(surface.get_height () - header_bar.get_allocated_height () - 2 * GRID_BORDER); - var opt_cell_height = (int)(available_grid_height / (n_rows * (1.3))); - - var available_screen_height = monitor_area.height * 0.9 - header_bar.get_allocated_height () - 2 * GRID_BORDER; - var max_cell_height = available_screen_height / (n_rows * (1.3)); - - var max_cell_size = (int)(double.min (max_cell_width, max_cell_height)); - if (max_cell_size < cell_size) { - cell_size = max_cell_size; - } else if (cell_size < opt_cell_height) { - cell_size = int.min (max_cell_size, opt_cell_height); - } + calc_cell_size (); + set_size (); + }); -warning ("setting window size"); - main_grid.set_size_request ( - (int)((double)(n_cols * cell_size) * 1.3), - (int)((double)(n_rows * cell_size) * 1.3) - ); + notify["cell-size"].connect (() => { + set_size (); }); + cell_grid.leave.connect (() => { row_clue_box.unhighlight_all (); column_clue_box.unhighlight_all (); @@ -470,6 +439,57 @@ warning ("setting window size"); cell_grid.stop_drawing.connect (stop_painting); } + private void calc_cell_size () { + // Update cell-size if required to fit on screen but without changing window size unnecessarily + // The dimensions may have increased or decreased so may need to increase or decrease cell size + // It is assumed up to 90% of the screen area can be used + var n_cols = controller.dimensions.width; + var n_rows = controller.dimensions.height; + + var monitor_area = Gdk.Rectangle () { + width = 1024, + height = 768 + }; + + Gdk.Surface? surface = get_surface (); + if (surface != null) { + monitor_area = Utils.get_monitor_area (surface); + } + + var available_screen_width = monitor_area.width * 0.9 - GRID_BORDER - GRID_COLUMN_SPACING; + var max_cell_width = available_screen_width / (n_cols * (1.3)); + var available_grid_height = (int)(surface.get_height () - header_bar.get_allocated_height () - GRID_BORDER); + var opt_cell_height = (int)(available_grid_height / (n_rows * (1.4))); + + var available_screen_height = monitor_area.height * 0.9 - header_bar.get_allocated_height () - GRID_BORDER; + var max_cell_height = available_screen_height / (n_rows * (1.4)); + + var max_cell_size = (int)(double.min (max_cell_width, max_cell_height)); + if (max_cell_size < cell_size) { + cell_size = max_cell_size; + } else if (opt_cell_height > 0 && cell_size < opt_cell_height) { + cell_size = int.min (max_cell_size, opt_cell_height); + } + } + + private void set_size () { + var n_cols = controller.dimensions.width; + var n_rows = controller.dimensions.height; + var width = (int)((double)(n_cols * cell_size) * 1.3); + var height = (int)((double)(n_rows * cell_size) * 1.4); + + set_default_size ( + width + GRID_BORDER + GRID_COLUMN_SPACING, + height + header_bar.get_allocated_height () + GRID_BORDER + ); + main_grid.set_size_request ( + width, + height + ); + + queue_draw (); + } + public string[] get_clues (bool is_column) { var label_box = is_column ? column_clue_box : row_clue_box; return label_box.get_clue_texts (); From 4400a7a84d56c7f7f0705a20849f6bbe039e5e00 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 15 Jul 2022 10:57:52 +0100 Subject: [PATCH 012/142] Fix keyboard painting on/off --- src/View.vala | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/View.vala b/src/View.vala index 69c8436..cfd8222 100644 --- a/src/View.vala +++ b/src/View.vala @@ -25,6 +25,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private const double TYPICAL_MAX_BLOCKS_RATIO = 0.3; private const double ZOOM_RATIO = 0.05; private const uint PROGRESS_DELAY_MSEC = 500; + private const string PAINT_FILL_ACCEL = "f"; // Must be lower case + private const string PAINT_EMPTY_ACCEL = "e"; // Must be lower case + private const string PAINT_UNKNOWN_ACCEL = "x"; // Must be lower case #if WITH_DEBUGGING public signal void debug_request (uint idx, bool is_column); @@ -100,7 +103,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { }; public static Gtk.Application app; - private Gtk.EventControllerKey key_controller; private ClueBox row_clue_box; private ClueBox column_clue_box; private CellGrid cell_grid; @@ -160,9 +162,9 @@ warning ("WITH DEBUGGING"); action_accelerators.set (ACTION_OPEN, "O"); action_accelerators.set (ACTION_SAVE, "S"); action_accelerators.set (ACTION_SAVE_AS, "S"); - action_accelerators.set (ACTION_PAINT_FILLED, "F"); - action_accelerators.set (ACTION_PAINT_EMPTY, "E"); - action_accelerators.set (ACTION_PAINT_UNKNOWN, "X"); + action_accelerators.set (ACTION_PAINT_FILLED, PAINT_FILL_ACCEL); + action_accelerators.set (ACTION_PAINT_EMPTY, PAINT_EMPTY_ACCEL); + action_accelerators.set (ACTION_PAINT_UNKNOWN, PAINT_UNKNOWN_ACCEL); action_accelerators.set (ACTION_CHECK_ERRORS, "F7"); action_accelerators.set (ACTION_RESTART, "F5"); action_accelerators.set (ACTION_RESTART, "R"); @@ -301,6 +303,7 @@ warning ("WITH DEBUGGING"); cell_grid = new CellGrid (this); main_grid = new Gtk.Grid () { + focusable = true, // Needed for key controller to work row_spacing = 0, margin_bottom = margin_end = GRID_BORDER, column_spacing = GRID_COLUMN_SPACING @@ -338,11 +341,11 @@ warning ("WITH DEBUGGING"); return Gdk.EVENT_PROPAGATE; }); - key_controller = new Gtk.EventControllerKey (); + var key_controller = new Gtk.EventControllerKey (); main_grid.add_controller (key_controller); key_controller.key_released.connect ((keyval, keycode, state) => { - if (keyval == drawing_with_key) { + if (Gdk.keyval_to_lower (keyval) == drawing_with_key) { stop_painting (); } @@ -781,12 +784,15 @@ warning ("WITH DEBUGGING"); private void action_paint_filled () { paint_cell_state (CellState.FILLED); + drawing_with_key = Gdk.keyval_from_name (PAINT_FILL_ACCEL); } private void action_paint_empty () { paint_cell_state (CellState.EMPTY); + drawing_with_key = Gdk.keyval_from_name (PAINT_EMPTY_ACCEL); } private void action_paint_unknown () { paint_cell_state (CellState.UNKNOWN); + drawing_with_key = Gdk.keyval_from_name (PAINT_UNKNOWN_ACCEL); } private void paint_cell_state (CellState cs) { if (cs == CellState.UNKNOWN && controller.game_state != GameState.SOLVING) { @@ -794,8 +800,6 @@ warning ("WITH DEBUGGING"); } drawing_with_state = cs; - var current_event = key_controller.get_current_event (); - drawing_with_key = ((Gdk.KeyEvent)current_event).get_keyval (); make_move_at_cell (); } From ee2a0b78b0ea08e5f13ccfd958ad2d64cd14d617 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 15 Jul 2022 11:53:16 +0100 Subject: [PATCH 013/142] Fix spurious pointer movement after key painting --- libcore/widgets/Cellgrid.vala | 26 ++++++++++++++++---------- src/View.vala | 2 +- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 33e8fb4..ad020e9 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -174,30 +174,36 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { draw_grid (cr); } - private void on_pointer_moved (double dx, double dy) { - if (draw_only || dx < 0 || dy < 0) { + private double previous_pointer_x = 0.0; + private double previous_pointer_y = 0.0; + private void on_pointer_moved (double x, double y) { + if (draw_only || x < 0 || y < 0) { return; } + + // Need to ignore spurious "movements" in Gtk4 + if (previous_pointer_x == x && previous_pointer_y == y) { + return; + } else { + previous_pointer_x = x; + previous_pointer_y = y; + } /* Calculate which cell the pointer is over */ - uint r = ((uint)((dy) / cell_height)); - uint c = ((uint)(dx / cell_width)); + uint r = ((uint)((y) / cell_height)); + uint c = ((uint)(x / cell_width)); if (r >= rows || c >= cols) { return; } /* Construct cell beneath pointer */ Cell cell = {r, c, array.get_data_from_rc (r, c)}; if (!cell.equal (current_cell)) { - update_current_cell (cell); + previous_cell = current_cell.clone (); + current_cell = cell.clone (); } return; } - private void update_current_cell (Cell target) { - previous_cell = current_cell.clone (); - current_cell = target.clone (); - } - private void draw_grid (Cairo.Context cr) { Gdk.cairo_set_source_rgba (cr, grid_color); cr.set_antialias (Cairo.Antialias.NONE); diff --git a/src/View.vala b/src/View.vala index cfd8222..1ef6e32 100644 --- a/src/View.vala +++ b/src/View.vala @@ -405,7 +405,7 @@ warning ("WITH DEBUGGING"); highlight_labels (previous_cell, false); highlight_labels (current_cell, true); - if (drawing_with_state != CellState.UNDEFINED) { + if (current_cell != NULL_CELL && drawing_with_state != CellState.UNDEFINED) { make_move_at_cell (); } }); From 29aea756d4e869db3a0030995c79f42f6a30c499 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 15 Jul 2022 12:13:28 +0100 Subject: [PATCH 014/142] Listen to all buttons --- libcore/widgets/Cellgrid.vala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index ad020e9..42f7677 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -101,10 +101,10 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { motion_controller.leave.connect (on_leave_notify); var button_controller = new Gtk.GestureClick (); + button_controller.set_button (0); // Listen to any button add_controller (button_controller); button_controller.pressed.connect ((n_press, x, y) => { - var button_event = (Gdk.ButtonEvent)(button_controller.get_current_event ()); - start_drawing (button_event.get_button (), n_press > 1); + start_drawing (button_controller.get_current_button (), n_press > 1); }); button_controller.released.connect ((n_press, x, y) => { stop_drawing (); From 2d29bd22b033ff86d3d4d4cce9bff0d92b7b8965 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 15 Jul 2022 13:09:17 +0100 Subject: [PATCH 015/142] Fix game generation --- libcore/Solver.vala | 6 +++--- src/Controller.vala | 10 ++++------ src/HeaderBar/AppPopover.vala | 2 +- src/services/RandomPatternGenerator.vala | 6 +++--- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/libcore/Solver.vala b/libcore/Solver.vala index 093dab9..4c03897 100644 --- a/libcore/Solver.vala +++ b/libcore/Solver.vala @@ -87,7 +87,6 @@ assert (row_clues.length == rows && col_clues.length == cols); should_check_solution = solution_grid != null; - if (should_check_solution) { solution.copy (solution_grid); } @@ -180,10 +179,13 @@ advanced_only = false; human_only = true; switch (grade) { + case Difficulty.TRIVIAL: + case Difficulty.VERY_EASY: case Difficulty.EASY: case Difficulty.MODERATE: case Difficulty.HARD: case Difficulty.CHALLENGING: + case Difficulty.UNDEFINED: break; case Difficulty.ADVANCED: @@ -204,8 +206,6 @@ human_only = false; break; - default: - assert_not_reached (); } } diff --git a/src/Controller.vala b/src/Controller.vala index e9546be..b5c349e 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -115,11 +115,12 @@ public class Gnonograms.Controller : GLib.Object { saved_state.bind ("mode", this, "game_state", SettingsBindFlags.DEFAULT); settings.bind ("grade", this, "generator_grade", SettingsBindFlags.DEFAULT); settings.bind ("clue-help", view, "strikeout-complete", SettingsBindFlags.DEFAULT); + } else { + restore_settings (); } view.present (); - restore_settings (); bind_property ("generator-grade", view, "generator-grade", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); bind_property ("is-readonly", view, "readonly", BindingFlags.SYNC_CREATE); @@ -226,18 +227,15 @@ public class Gnonograms.Controller : GLib.Object { private void restore_settings () { if (saved_state != null) { - int cell_size; - saved_state.get ("cell-size", "i", out cell_size); + int cell_size = saved_state.get_int ("cell-size"); current_game_path = saved_state.get_string ("current-game-path"); if (cell_size > 0) { view.cell_size = cell_size; } } else { /* Error normally thrown running uninstalled */ - warning ("Unable to restore settings - using defaults"); /* Maybe running uninstalled */ /* Default puzzle parameters */ - game_state = GameState.SOLVING; - generator_grade = Difficulty.MODERATE; + view.cell_size = 24; current_game_path = ""; } diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index a5e2245..309bcca 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -26,7 +26,7 @@ public Difficulty grade { get { - return (Difficulty)(int.parse (grade_setting.get_active_text ())); + return (Difficulty)(int.parse (grade_setting.get_active_id ())); } set { diff --git a/src/services/RandomPatternGenerator.vala b/src/services/RandomPatternGenerator.vala index 337eb93..29a38aa 100644 --- a/src/services/RandomPatternGenerator.vala +++ b/src/services/RandomPatternGenerator.vala @@ -78,6 +78,8 @@ public class Gnonograms.RandomPatternGenerator : Object { edge_bias = 0; switch (grade) { + case Difficulty.TRIVIAL: + case Difficulty.VERY_EASY: case Difficulty.EASY: threshold = 60; min_freedom = 1; @@ -106,11 +108,9 @@ public class Gnonograms.RandomPatternGenerator : Object { min_freedom = 4; break; case Difficulty.UNDEFINED: + case Difficulty.COMPUTER: /* May not be defined on creation */ break; - default: - critical ("unexpected grade %s", grade.to_string ()); - assert_not_reached (); } } From 81f11326f8a64e6cfcf7da1225075abfeb60196d Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 15 Jul 2022 14:04:31 +0100 Subject: [PATCH 016/142] Fix hinting --- libcore/Solver.vala | 5 ----- libcore/widgets/Cluebox.vala | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/libcore/Solver.vala b/libcore/Solver.vala index 4c03897..164724e 100644 --- a/libcore/Solver.vala +++ b/libcore/Solver.vala @@ -111,10 +111,6 @@ return valid (); } - /** Initiate solving, specifying whether or not to use the advanced - * procedures. Also specify whether in debugging mode and whether to solve one step - * at a time (used for hinting if implemented). - **/ public async Difficulty solve_clues (string[] row_clues, string[] col_clues, My2DCellArray? start_grid = null, @@ -265,7 +261,6 @@ public Gee.ArrayQueue hint (string[] row_clues, string[] col_clues, My2DCellArray working) { initialize (row_clues, col_clues, working, null); - bool changed = false; uint count = 0; var moves = new Gee.ArrayQueue (); diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index 624e34c..df6fe99 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -67,7 +67,7 @@ public class Gnonograms.ClueBox : Gtk.Box { } public string[] get_clue_texts () { - string[] clue_texts = new string [clues.size]; + string[] clue_texts = {}; foreach (var clue in clues) { clue_texts += clue.text; } From 01f18d45595cd8f4eab7707879392cfc66ab361e Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 15 Jul 2022 14:28:44 +0100 Subject: [PATCH 017/142] Position toast top start --- src/View.vala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/View.vala b/src/View.vala index 1ef6e32..171af31 100644 --- a/src/View.vala +++ b/src/View.vala @@ -302,12 +302,18 @@ warning ("WITH DEBUGGING"); column_clue_box = new ClueBox (Gtk.Orientation.HORIZONTAL, this); cell_grid = new CellGrid (this); + toast_overlay = new Adw.ToastOverlay () { + vexpand = false, + valign = Gtk.Align.START + }; + main_grid = new Gtk.Grid () { focusable = true, // Needed for key controller to work row_spacing = 0, margin_bottom = margin_end = GRID_BORDER, column_spacing = GRID_COLUMN_SPACING }; + main_grid.attach (toast_overlay, 0, 0, 1, 1); /* show temporary messages */ main_grid.attach (row_clue_box, 0, 1, 1, 1); /* Clues fordimensions.height*/ main_grid.attach (column_clue_box, 1, 0, 1, 1); /* Clues for columns */ main_grid.attach (cell_grid, 1, 1, 1, 1); @@ -352,11 +358,7 @@ warning ("WITH DEBUGGING"); return; }); - toast_overlay = new Adw.ToastOverlay () { - child = main_grid - }; - - child = toast_overlay; + child = main_grid; var flags = BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE; bind_property ("restart-destructive", restart_button, "restart-destructive", BindingFlags.SYNC_CREATE); From 27975d2bafc62276ee80ef0d7ac7e1ebfd8cd008 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 15 Jul 2022 14:46:36 +0100 Subject: [PATCH 018/142] Fix follow system style --- src/Application.vala | 7 ------- src/View.vala | 11 +++++++++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index 15a723a..b0b22bd 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -43,14 +43,7 @@ public class Gnonograms.App : Gtk.Application { add_action (quit_action); set_accels_for_action ("app.quit", {"q"}); - // var granite_settings = Granite.Settings.get_default (); - // var gtk_settings = Gtk.Settings.get_default (); - // gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; - - // granite_settings.notify["prefers-color-scheme"].connect (() => { - // gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; - // }); } public override void open (File[] files, string hint) { diff --git a/src/View.vala b/src/View.vala index 171af31..07ea609 100644 --- a/src/View.vala +++ b/src/View.vala @@ -190,6 +190,17 @@ warning ("WITH DEBUGGING"); } construct { + var granite_settings = Granite.Settings.get_default (); + var gtk_settings = Gtk.Settings.get_default (); + + if (gtk_settings != null && granite_settings != null) { + gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; + + granite_settings.notify["prefers-color-scheme"].connect (() => { + gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; + }); + } + weak Gtk.IconTheme default_theme = Gtk.IconTheme.get_for_display (Gdk.Display.get_default ()); default_theme.add_resource_path ("/com/github/jeremypw/gnonograms"); From 8623d589e971d7b7b2acd7af0b4f196de2b4b0bb Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 24 Dec 2023 17:00:47 +0000 Subject: [PATCH 019/142] Support Shift-click for EMPTY --- libcore/widgets/Cellgrid.vala | 7 +++++-- src/View.vala | 13 +++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 42f7677..04f09be 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -19,7 +19,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { public signal void cursor_moved (Cell from, Cell to); - public signal void start_drawing (uint button, bool double_click); + public signal void start_drawing (uint button, Gdk.ModifierType state, bool double_click); public signal void stop_drawing (); public signal void leave (); @@ -104,7 +104,10 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { button_controller.set_button (0); // Listen to any button add_controller (button_controller); button_controller.pressed.connect ((n_press, x, y) => { - start_drawing (button_controller.get_current_button (), n_press > 1); + start_drawing ( + button_controller.get_current_button (), + button_controller.get_current_event_state (), + n_press > 1); }); button_controller.released.connect ((n_press, x, y) => { stop_drawing (); diff --git a/src/View.vala b/src/View.vala index 07ea609..2f985c9 100644 --- a/src/View.vala +++ b/src/View.vala @@ -108,7 +108,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private CellGrid cell_grid; private ProgressIndicator progress_indicator; private Gtk.MenuButton menu_button; - private CellState drawing_with_state = CellState.UNDEFINED; + private CellState drawing_with_state = UNDEFINED; private Gtk.HeaderBar header_bar; private Granite.ModeSwitch mode_switch; private Gtk.Grid main_grid; @@ -442,11 +442,16 @@ warning ("WITH DEBUGGING"); column_clue_box.unhighlight_all (); }); - cell_grid.start_drawing.connect ((button, double_click) => { + cell_grid.start_drawing.connect ((button, state, double_click) => { if (double_click || button == Gdk.BUTTON_MIDDLE) { - drawing_with_state = controller.game_state == GameState.SOLVING ? CellState.UNKNOWN : CellState.EMPTY; + drawing_with_state = controller.game_state == SOLVING ? CellState.UNKNOWN : CellState.EMPTY; } else { - drawing_with_state = button == Gdk.BUTTON_PRIMARY ? CellState.FILLED : CellState.EMPTY; + if (state == SHIFT_MASK && button == Gdk.BUTTON_PRIMARY) { + drawing_with_state = CellState.EMPTY; + } else { + drawing_with_state = button == Gdk.BUTTON_PRIMARY ? CellState.FILLED : CellState.EMPTY; + } + } make_move_at_cell (); From 107cd494b99a4a431a29c2915af47e4e0e85a748 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 24 Dec 2023 17:29:17 +0000 Subject: [PATCH 020/142] Move cursor to (0, 0) if out of grid and cursor moved --- libcore/widgets/Cellgrid.vala | 3 +-- src/View.vala | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 04f09be..1249810 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -18,7 +18,6 @@ */ public class Gnonograms.CellGrid : Gtk.DrawingArea { - public signal void cursor_moved (Cell from, Cell to); public signal void start_drawing (uint button, Gdk.ModifierType state, bool double_click); public signal void stop_drawing (); public signal void leave (); @@ -106,7 +105,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { button_controller.pressed.connect ((n_press, x, y) => { start_drawing ( button_controller.get_current_button (), - button_controller.get_current_event_state (), + button_controller.get_current_event_state (), n_press > 1); }); button_controller.released.connect ((n_press, x, y) => { diff --git a/src/View.vala b/src/View.vala index 2f985c9..4d820b2 100644 --- a/src/View.vala +++ b/src/View.vala @@ -775,6 +775,7 @@ warning ("WITH DEBUGGING"); } private void move_cursor (int row_delta, int col_delta) { if (current_cell == NULL_CELL) { + update_current_cell ({0, 0, CellState.UNDEFINED}); return; } From f34c754ef327e934d445c6fd7a58268c5782ec46 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 24 Dec 2023 17:31:23 +0000 Subject: [PATCH 021/142] Fix lint --- src/HeaderBar/AppPopover.vala | 205 +++++++++++++++---------------- src/HeaderBar/RestartButton.vala | 4 +- 2 files changed, 104 insertions(+), 105 deletions(-) diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 309bcca..16f6f35 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -16,120 +16,119 @@ * * Author: Jeremy Wootten */ - public class Gnonograms.AppPopover : Gtk.Popover { - public signal void apply_settings (); - - private Gtk.ComboBoxText grade_setting; - private Gtk.SpinButton row_setting; - private Gtk.SpinButton column_setting; - private Gtk.Entry title_setting; - - public Difficulty grade { - get { - return (Difficulty)(int.parse (grade_setting.get_active_id ())); - } - - set { - grade_setting.active_id = ((uint)value).clamp (MIN_GRADE, Difficulty.MAXIMUM).to_string (); - } +public class Gnonograms.AppPopover : Gtk.Popover { + public signal void apply_settings (); + + private Gtk.ComboBoxText grade_setting; + private Gtk.SpinButton row_setting; + private Gtk.SpinButton column_setting; + private Gtk.Entry title_setting; + + public Difficulty grade { + get { + return (Difficulty)(int.parse (grade_setting.get_active_id ())); } - public uint rows { - get { - return (uint)(row_setting.@value); - } + set { + grade_setting.active_id = ((uint)value).clamp (MIN_GRADE, Difficulty.MAXIMUM).to_string (); + } + } - set { - row_setting.@value = value; - } + public uint rows { + get { + return (uint)(row_setting.@value); } - public uint columns { - get { - return (uint)(column_setting.@value); - } + set { + row_setting.@value = value; + } + } - set { - column_setting.@value = value; - } + public uint columns { + get { + return (uint)(column_setting.@value); } - public string title { - get { - return title_setting.text; - } + set { + column_setting.@value = value; + } + } - set { - title_setting.text = value; - } + public string title { + get { + return title_setting.text; } - construct { - grade_setting = new Gtk.ComboBoxText (); - foreach (Difficulty d in Difficulty.all_human ()) { - grade_setting.append (((uint)d).to_string (), d.to_string ()); - } - - row_setting = new Gtk.SpinButton ( - new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), - 5.0, - 0 - ) { - snap_to_ticks = true, - orientation = Gtk.Orientation.HORIZONTAL, - width_chars = 3, - }; - - column_setting = new Gtk.SpinButton ( - new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), - 5.0, - 0 - ) { - snap_to_ticks = true, - orientation = Gtk.Orientation.HORIZONTAL, - width_chars = 3, - }; - - title_setting = new Gtk.Entry () { - placeholder_text = _("Enter title of game here") - }; - - var settings_grid = new Gtk.Grid () { - orientation = Gtk.Orientation.VERTICAL, - row_spacing = 12, - column_spacing = 12, - margin_start = margin_end = margin_top = 12, - margin_bottom = 24 - }; - settings_grid.attach (new Gtk.Label (_("Name:")), 0, 0, 1); - settings_grid.attach (title_setting, 1, 0, 3); - settings_grid.attach (new Gtk.Label (_("Difficulty:")), 0, 1, 1); - settings_grid.attach (grade_setting, 1, 1, 3); - settings_grid.attach (new Gtk.Label (_("Rows:")), 0, 2, 1); - settings_grid.attach (row_setting, 1, 2, 1); - settings_grid.attach (new Gtk.Label (_("Columns:")), 0, 3, 1); - settings_grid.attach (column_setting, 1, 3, 1); - - var cancel_button = new Gtk.Button.with_label (_("Cancel")); - var apply_button = new Gtk.Button.with_label (_("Apply")); - default_widget = apply_button; - cancel_button.clicked.connect (() => { - hide (); - }); - apply_button.clicked.connect (() => { - apply_settings (); - hide (); - }); - var popover_actions = new Gtk.ActionBar (); - popover_actions.pack_start (cancel_button); - popover_actions.pack_end (apply_button); - - var main_widget = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); - // main_grid.append (size_grid); - main_widget.append (settings_grid); - main_widget.append (popover_actions); - - child = main_widget; + set { + title_setting.text = value; } } + construct { + grade_setting = new Gtk.ComboBoxText (); + foreach (Difficulty d in Difficulty.all_human ()) { + grade_setting.append (((uint)d).to_string (), d.to_string ()); + } + + row_setting = new Gtk.SpinButton ( + new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), + 5.0, + 0 + ) { + snap_to_ticks = true, + orientation = Gtk.Orientation.HORIZONTAL, + width_chars = 3, + }; + + column_setting = new Gtk.SpinButton ( + new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), + 5.0, + 0 + ) { + snap_to_ticks = true, + orientation = Gtk.Orientation.HORIZONTAL, + width_chars = 3, + }; + + title_setting = new Gtk.Entry () { + placeholder_text = _("Enter title of game here") + }; + + var settings_grid = new Gtk.Grid () { + orientation = Gtk.Orientation.VERTICAL, + row_spacing = 12, + column_spacing = 12, + margin_start = margin_end = margin_top = 12, + margin_bottom = 24 + }; + settings_grid.attach (new Gtk.Label (_("Name:")), 0, 0, 1); + settings_grid.attach (title_setting, 1, 0, 3); + settings_grid.attach (new Gtk.Label (_("Difficulty:")), 0, 1, 1); + settings_grid.attach (grade_setting, 1, 1, 3); + settings_grid.attach (new Gtk.Label (_("Rows:")), 0, 2, 1); + settings_grid.attach (row_setting, 1, 2, 1); + settings_grid.attach (new Gtk.Label (_("Columns:")), 0, 3, 1); + settings_grid.attach (column_setting, 1, 3, 1); + + var cancel_button = new Gtk.Button.with_label (_("Cancel")); + var apply_button = new Gtk.Button.with_label (_("Apply")); + default_widget = apply_button; + cancel_button.clicked.connect (() => { + hide (); + }); + apply_button.clicked.connect (() => { + apply_settings (); + hide (); + }); + var popover_actions = new Gtk.ActionBar (); + popover_actions.pack_start (cancel_button); + popover_actions.pack_end (apply_button); + + var main_widget = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); + // main_grid.append (size_grid); + main_widget.append (settings_grid); + main_widget.append (popover_actions); + + child = main_widget; + } +} diff --git a/src/HeaderBar/RestartButton.vala b/src/HeaderBar/RestartButton.vala index ebdcbc0..81a7c3c 100644 --- a/src/HeaderBar/RestartButton.vala +++ b/src/HeaderBar/RestartButton.vala @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2010-2022 Jeremy Wootten * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * * Author: Jeremy Wootten */ - + private class Gnonograms.RestartButton : Gnonograms.HeaderButton { public bool restart_destructive { get; set; } From d400759eb5602779ba53a7abddf3356c78ebe7a9 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 24 Dec 2023 17:50:33 +0000 Subject: [PATCH 022/142] Override measure () for CellGrid --- libcore/widgets/Cellgrid.vala | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 1249810..1e056c0 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -318,6 +318,27 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { return; } + public override void measure ( + Gtk.Orientation orientation, + int for_size, + out int minimum, + out int natural, + out int minimum_baseline, + out int natural_baseline + ) { + if (orientation == HORIZONTAL) { + minimum = (int)(cell_width * cols); + natural = minimum; + minimum_baseline = -1; + natural_baseline = -1; + } else { + minimum = (int)(cell_height * rows); + natural = minimum; + minimum_baseline = -1; + natural_baseline = -1; + } + } + private class CellPattern { public Cairo.Pattern pattern; public double size { get; private set; } From 637892473a6a88ebaf509b0ad067bb8e6bc76a06 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 24 Dec 2023 18:22:10 +0000 Subject: [PATCH 023/142] Ensure clues cleared on dimension change --- libcore/widgets/Clue.vala | 2 +- libcore/widgets/Cluebox.vala | 25 +++++++++---------------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/libcore/widgets/Clue.vala b/libcore/widgets/Clue.vala index f0c5f6e..01f932f 100644 --- a/libcore/widgets/Clue.vala +++ b/libcore/widgets/Clue.vala @@ -19,7 +19,7 @@ class Gnonograms.Clue : Object { public Gtk.Label label { get; construct; } - public ClueBox cluebox {get; construct; } + public unowned ClueBox cluebox { get; construct; } private int _fontsize; private string _text; /* text of clue in horizontal form */ diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index df6fe99..08272b4 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -46,22 +46,15 @@ public class Gnonograms.ClueBox : Gtk.Box { view.controller.dimensions.height : view.controller.dimensions.width; - if (new_n_clues != clues.size) { - n_cells = new_n_cells; - clues.@foreach ((clue) => { - remove (clue.label); - }); - - clues.clear (); - for (int index = 0; index < new_n_clues; index++) { - var clue = new Clue (orientation == Gtk.Orientation.HORIZONTAL, this); - clues.add (clue); - append (clue.label); - } - } else if (new_n_cells != n_cells) { - n_cells = new_n_cells; - } else { - return; + foreach (var clue in clues) { + remove (clue.label); + } + clues.clear (); + n_cells = new_n_cells; + for (int index = 0; index < new_n_clues; index++) { + var clue = new Clue (orientation == Gtk.Orientation.HORIZONTAL, this); + clues.add (clue); + append (clue.label); } }); } From 19494b7957d11577e664ed4e8b74940a7b0d4f1a Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 25 Dec 2023 12:53:44 +0000 Subject: [PATCH 024/142] Fix resizing (with hack) --- libcore/widgets/Cellgrid.vala | 13 +++++------ libcore/widgets/Clue.vala | 7 +++--- libcore/widgets/Cluebox.vala | 4 +--- src/Controller.vala | 24 -------------------- src/View.vala | 41 +++++++++++++---------------------- 5 files changed, 25 insertions(+), 64 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 1e056c0..b05a977 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -118,8 +118,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { queue_draw (); }); - view.notify["cell-size"].connect (dimensions_updated); - view.controller.notify["dimensions"].connect (dimensions_updated); + view.notify["cell-size"].connect (size_updated); + view.controller.notify["dimensions"].connect (size_updated); view.controller.notify["game-state"].connect (() => { var gs = view.controller.game_state; unknown_color = colors[(int)gs, (int)CellState.UNKNOWN]; @@ -135,7 +135,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } }); - dimensions_updated (); + size_updated (); } private void set_colors () { @@ -150,16 +150,15 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { colors[solving, (int)CellState.FILLED].parse (Gnonograms.SOLVING_FILLED_COLOR); } - private void dimensions_updated () { + private void size_updated () { rows = (int)view.controller.dimensions.height; cols = (int)view.controller.dimensions.width; cell_width = view.cell_size; cell_height = view.cell_size; /* Cause refresh of existing pattern */ highlight_pattern = new CellPattern.highlight (cell_width, cell_height); - var width = cols * view.cell_size + (int)MINOR_GRID_LINE_WIDTH; - var height = rows * view.cell_size + (int)MINOR_GRID_LINE_WIDTH; - set_size_request (width, height); + content_width = cols * view.cell_size + (int)MINOR_GRID_LINE_WIDTH; + content_height = rows * view.cell_size + (int)MINOR_GRID_LINE_WIDTH; } private void draw_func (Gtk.DrawingArea drawing_area, Cairo.Context cr, int x, int y) { diff --git a/libcore/widgets/Clue.vala b/libcore/widgets/Clue.vala index 01f932f..b2cdf55 100644 --- a/libcore/widgets/Clue.vala +++ b/libcore/widgets/Clue.vala @@ -57,8 +57,7 @@ class Gnonograms.Clue : Object { text = "0"; label.realize.connect_after (() => { - _fontsize = (int)((double)cluebox.cell_size * 0.4); - + _fontsize = (int)((double)cluebox.view.cell_size * 0.4); update_markup (); }); @@ -66,8 +65,8 @@ class Gnonograms.Clue : Object { update_tooltip (); }); - cluebox.notify["cell-size"].connect (() => { - _fontsize = (int)((double)cluebox.cell_size * 0.4); + cluebox.view.notify["cell-size"].connect (() => { + _fontsize = (int)((double)cluebox.view.cell_size * 0.4); update_markup (); }); } diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index 08272b4..260999e 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -18,9 +18,8 @@ */ public class Gnonograms.ClueBox : Gtk.Box { - public View view { get; construct; } + public unowned View view { get; construct; } public uint n_cells { get; set; default = 0; } // The number of cells each clue addresses, monitored by clues - public uint cell_size { get; set; default = 0; } // Dimensions of individual grid cells, monitored by clues private Gee.ArrayList clues; public ClueBox (Gtk.Orientation _orientation, View view) { @@ -36,7 +35,6 @@ public class Gnonograms.ClueBox : Gtk.Box { construct { clues = new Gee.ArrayList (); - view.bind_property ("cell-size", this, "cell-size", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE); view.controller.notify ["dimensions"].connect (() => { var new_n_clues = orientation == Gtk.Orientation.HORIZONTAL ? view.controller.dimensions.width : diff --git a/src/Controller.vala b/src/Controller.vala index b5c349e..be74296 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -52,10 +52,6 @@ public class Gnonograms.Controller : GLib.Object { history = new Gnonograms.History (); view.close_request.connect (on_view_deleted); - view.notify["default-width"].connect (on_view_configure); - view.notify["default-height"].connect (on_view_configure); - view.notify["maximized"].connect (on_view_configure); - view.notify["fullscreened"].connect (on_view_configure); #if WITH_DEBUGGING view.debug_request.connect (on_debug_request); #endif @@ -509,26 +505,6 @@ public class Gnonograms.Controller : GLib.Object { return Gdk.EVENT_PROPAGATE; } - private uint configure_id = 0; - private void on_view_configure () { - if (saved_state == null) { - return; - } - - if (configure_id != 0) { - GLib.Source.remove (configure_id); - } - - configure_id = Timeout.add (100, () => { - configure_id = 0; - saved_state.set ("cell-size", "i", view.cell_size); - - return Source.REMOVE; - }); - - return; - } - public void save_game () { if (is_readonly || current_game_path == "") { save_game_as (); diff --git a/src/View.vala b/src/View.vala index 4d820b2..07a8064 100644 --- a/src/View.vala +++ b/src/View.vala @@ -132,7 +132,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { Object ( model: _model, controller: controller, - resizable: true, title: _("Gnonograms") ); } @@ -190,6 +189,7 @@ warning ("WITH DEBUGGING"); } construct { + resizable = false; var granite_settings = Granite.Settings.get_default (); var gtk_settings = Gtk.Settings.get_default (); @@ -313,6 +313,13 @@ warning ("WITH DEBUGGING"); column_clue_box = new ClueBox (Gtk.Orientation.HORIZONTAL, this); cell_grid = new CellGrid (this); + var vert_sizegroup = new Gtk.SizeGroup (VERTICAL); + vert_sizegroup.add_widget (row_clue_box); + vert_sizegroup.add_widget (cell_grid); + var horiz_sizegroup = new Gtk.SizeGroup (HORIZONTAL); + horiz_sizegroup.add_widget (column_clue_box); + horiz_sizegroup.add_widget (cell_grid); + toast_overlay = new Adw.ToastOverlay () { vexpand = false, valign = Gtk.Align.START @@ -429,14 +436,8 @@ warning ("WITH DEBUGGING"); controller.notify["dimensions"].connect (() => { calc_cell_size (); - set_size (); - }); - - notify["cell-size"].connect (() => { - set_size (); }); - cell_grid.leave.connect (() => { row_clue_box.unhighlight_all (); column_clue_box.unhighlight_all (); @@ -493,24 +494,6 @@ warning ("WITH DEBUGGING"); } } - private void set_size () { - var n_cols = controller.dimensions.width; - var n_rows = controller.dimensions.height; - var width = (int)((double)(n_cols * cell_size) * 1.3); - var height = (int)((double)(n_rows * cell_size) * 1.4); - - set_default_size ( - width + GRID_BORDER + GRID_COLUMN_SPACING, - height + header_bar.get_allocated_height () + GRID_BORDER - ); - main_grid.set_size_request ( - width, - height - ); - - queue_draw (); - } - public string[] get_clues (bool is_column) { var label_box = is_column ? column_clue_box : row_clue_box; return label_box.get_clue_texts (); @@ -751,7 +734,13 @@ warning ("WITH DEBUGGING"); if (increase) { cell_size += (int)delta; } else { - cell_size -= (int)delta; + //FIXME This is a hack to fix redrawing the window when the grid gets smaller. For some + //reason the window only properly redraws when the size increases + cell_size -= 2 * (int)delta; + Idle.add (() => { + cell_size += (int)delta; + return Source.REMOVE; + }); } } From 04717af216a020c220638587d885771d98f83a3f Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 25 Dec 2023 13:02:41 +0000 Subject: [PATCH 025/142] Fix Control-Scroll to zoom --- src/View.vala | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/View.vala b/src/View.vala index 07a8064..ccc6c7b 100644 --- a/src/View.vala +++ b/src/View.vala @@ -342,13 +342,8 @@ warning ("WITH DEBUGGING"); main_grid.add_controller (scroll_controller); scroll_controller.scroll.connect ((dx, dy) => { - var modifiers = scroll_controller. - get_current_event_device (). - get_seat (). - get_keyboard (). - get_modifier_state (); - - if (modifiers == Gdk.ModifierType.CONTROL_MASK) { + var modifiers = scroll_controller.get_current_event_state (); + if ((modifiers & Gdk.ModifierType.CONTROL_MASK) > 0) { Idle.add (() => { if (dy > 0.0) { action_zoom_in (); From dfd8c88c8ab4888c5bb9ced860ecff0a0980563b Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 25 Dec 2023 13:17:13 +0000 Subject: [PATCH 026/142] Fix Shift-Click --- src/View.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/View.vala b/src/View.vala index ccc6c7b..e026902 100644 --- a/src/View.vala +++ b/src/View.vala @@ -442,7 +442,7 @@ warning ("WITH DEBUGGING"); if (double_click || button == Gdk.BUTTON_MIDDLE) { drawing_with_state = controller.game_state == SOLVING ? CellState.UNKNOWN : CellState.EMPTY; } else { - if (state == SHIFT_MASK && button == Gdk.BUTTON_PRIMARY) { + if (((state & Gdk.ModifierType.SHIFT_MASK) > 0) && button == Gdk.BUTTON_PRIMARY) { drawing_with_state = CellState.EMPTY; } else { drawing_with_state = button == Gdk.BUTTON_PRIMARY ? CellState.FILLED : CellState.EMPTY; From 846b2e259670d5aa373a7ac6364b97fa120fb61e Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 25 Dec 2023 16:40:43 +0000 Subject: [PATCH 027/142] Fix BUTTON_SECONDARY press --- src/View.vala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/View.vala b/src/View.vala index e026902..6789db8 100644 --- a/src/View.vala +++ b/src/View.vala @@ -442,12 +442,12 @@ warning ("WITH DEBUGGING"); if (double_click || button == Gdk.BUTTON_MIDDLE) { drawing_with_state = controller.game_state == SOLVING ? CellState.UNKNOWN : CellState.EMPTY; } else { - if (((state & Gdk.ModifierType.SHIFT_MASK) > 0) && button == Gdk.BUTTON_PRIMARY) { + if (button == Gdk.BUTTON_SECONDARY || + ((state & Gdk.ModifierType.SHIFT_MASK) > 0) && button == Gdk.BUTTON_PRIMARY) { drawing_with_state = CellState.EMPTY; } else { - drawing_with_state = button == Gdk.BUTTON_PRIMARY ? CellState.FILLED : CellState.EMPTY; + drawing_with_state = CellState.FILLED; } - } make_move_at_cell (); From a2b5d3157790aa8d812b66259027b712356e763c Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 25 Dec 2023 17:20:40 +0000 Subject: [PATCH 028/142] Stop headerbar focusing --- src/View.vala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/View.vala b/src/View.vala index 6789db8..78e8b3f 100644 --- a/src/View.vala +++ b/src/View.vala @@ -291,7 +291,8 @@ warning ("WITH DEBUGGING"); header_bar = new Gtk.HeaderBar () { show_title_buttons = true, - title_widget = progress_stack + title_widget = progress_stack, + can_focus = false }; header_bar.add_css_class ("gnonograms-header"); header_bar.pack_start (load_game_button); From 5a779411f78cb6dfd4ecfdf0359fcf03abbb2d0c Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 25 Dec 2023 18:33:06 +0000 Subject: [PATCH 029/142] Set size request on clueboxes --- libcore/widgets/Cluebox.vala | 12 ++++++++++++ src/View.vala | 5 +++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index 260999e..b20c5f0 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -54,7 +54,19 @@ public class Gnonograms.ClueBox : Gtk.Box { clues.add (clue); append (clue.label); } + + set_size (); }); + + view.notify["cell-size"].connect (set_size); + } + + private void set_size () { + if (orientation == HORIZONTAL) { + height_request = (int) ((double)(view.controller.dimensions.height * view.cell_size) / 3.0); + } else { + width_request = (int) ((double) (view.controller.dimensions.width * view.cell_size) / 3.0); + } } public string[] get_clue_texts () { diff --git a/src/View.vala b/src/View.vala index 78e8b3f..9695bc9 100644 --- a/src/View.vala +++ b/src/View.vala @@ -730,9 +730,10 @@ warning ("WITH DEBUGGING"); if (increase) { cell_size += (int)delta; } else { + cell_size -= (int)delta; //FIXME This is a hack to fix redrawing the window when the grid gets smaller. For some - //reason the window only properly redraws when the size increases - cell_size -= 2 * (int)delta; + //reason the window only properly redraws when the size increases. Review for later versions of Gtk4/Elementary + cell_size -= (int)delta; Idle.add (() => { cell_size += (int)delta; return Source.REMOVE; From ca131128c37abfb13c3994e345af7eeedecc8913 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 27 Dec 2023 17:23:41 +0000 Subject: [PATCH 030/142] Change header css --- data/Application.css | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/data/Application.css b/data/Application.css index 01f2607..66e99e2 100644 --- a/data/Application.css +++ b/data/Application.css @@ -21,10 +21,8 @@ @define-color GNONOGRAMS_DARK_PURPLE #180297; @define-color GNONOGRAMS_PALE_PURPLE #cdc9e0; .gnonograms-header { - border: solid; - border-top-width: 1px; + border-style: ridge; + border-top-width: 10px; border-color: @GNONOGRAMS_DARK_PURPLE; - background-image: linear-gradient(to left, - alpha(@GNONOGRAMS_DARK_PURPLE, 1.0), alpha(@GNONOGRAMS_PALE_PURPLE, 0)); } From d8fab1eea90f01d498c95e229b16c0a829054da3 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 27 Dec 2023 17:25:13 +0000 Subject: [PATCH 031/142] Code cleanup, shorter lines --- src/View.vala | 92 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 26 deletions(-) diff --git a/src/View.vala b/src/View.vala index 9695bc9..f364a71 100644 --- a/src/View.vala +++ b/src/View.vala @@ -194,10 +194,12 @@ warning ("WITH DEBUGGING"); var gtk_settings = Gtk.Settings.get_default (); if (gtk_settings != null && granite_settings != null) { - gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; + gtk_settings.gtk_application_prefer_dark_theme = + granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; granite_settings.notify["prefers-color-scheme"].connect (() => { - gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; + gtk_settings.gtk_application_prefer_dark_theme = + granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; }); } @@ -215,40 +217,68 @@ warning ("WITH DEBUGGING"); app.set_accels_for_action (ACTION_PREFIX + action, accels_array); } - load_game_button = new HeaderButton ("document-open", ACTION_PREFIX + ACTION_OPEN, _("Load Game")); - save_game_button = new HeaderButton ("document-save", ACTION_PREFIX + ACTION_SAVE, _("Save Game")); - save_game_as_button = new HeaderButton ("document-save-as", ACTION_PREFIX + ACTION_SAVE_AS, _("Save Game to Different File")); - undo_button = new HeaderButton ("edit-undo", ACTION_PREFIX + ACTION_UNDO, _("Undo Last Move")); - redo_button = new HeaderButton ("edit-redo", ACTION_PREFIX + ACTION_REDO, _("Redo Last Move")); - check_correct_button = new HeaderButton ("media-seek-backward", ACTION_PREFIX + ACTION_CHECK_ERRORS, _("Check for Errors")); - restart_button = new RestartButton ("view-refresh", ACTION_PREFIX + ACTION_RESTART, _("Start again")) { + load_game_button = new HeaderButton ( + "document-open", + ACTION_PREFIX + ACTION_OPEN, + _("Load Game") + ); + save_game_button = new HeaderButton ( + "document-save", + ACTION_PREFIX + ACTION_SAVE, + _("Save Game") + ); + save_game_as_button = new HeaderButton ( + "document-save-as", + ACTION_PREFIX + ACTION_SAVE_AS, + _("Save Game to Different File") + ); + undo_button = new HeaderButton ( + "edit-undo", + ACTION_PREFIX + ACTION_UNDO, + _("Undo Last Move") + ); + redo_button = new HeaderButton ( + "edit-redo", + ACTION_PREFIX + ACTION_REDO, + _("Redo Last Move") + ); + check_correct_button = new HeaderButton ( + "media-seek-backward", + ACTION_PREFIX + ACTION_CHECK_ERRORS, + _("Check for Errors") + ); + restart_button = new RestartButton ( + "view-refresh", + ACTION_PREFIX + ACTION_RESTART, + _("Start again") + ) { margin_end = 12, margin_start = 12, }; - - hint_button = new HeaderButton ("help-contents", ACTION_PREFIX + ACTION_HINT, _("Suggest next move")); - auto_solve_button = new HeaderButton ("system", ACTION_PREFIX + ACTION_SOLVE, _("Solve by Computer")); - generate_button = new HeaderButton ("list-add", ACTION_PREFIX + ACTION_GENERATING_MODE, _("Generate New Puzzle")); - - menu_button = new Gtk.MenuButton () { - tooltip_markup = Granite.markup_accel_tooltip ( - app.get_accels_for_action (ACTION_PREFIX + ACTION_OPTIONS), _("Options") - ), - icon_name = "open-menu" - }; + hint_button = new HeaderButton ( + "help-contents", + ACTION_PREFIX + ACTION_HINT, + _("Suggest next move") + ); + auto_solve_button = new HeaderButton ( + "system", + ACTION_PREFIX + ACTION_SOLVE, + _("Solve by Computer") + ); + generate_button = new HeaderButton ( + "list-add", + ACTION_PREFIX + ACTION_GENERATING_MODE, + _("Generate New Puzzle") + ); var app_popover = new AppPopover () { has_arrow = false }; - - menu_button.set_popover (app_popover); - app_popover.apply_settings.connect (() => { controller.generator_grade = app_popover.grade; controller.dimensions = {app_popover.columns, app_popover.rows}; controller.game_name = app_popover.title; // Must come after changing dimensions }); - app_popover.show.connect (() => { /* Allow parent to set values first */ app_popover.grade = controller.generator_grade; app_popover.rows = controller.dimensions.height; @@ -256,8 +286,19 @@ warning ("WITH DEBUGGING"); app_popover.title = controller.game_name; }); + menu_button = new Gtk.MenuButton () { + tooltip_markup = Granite.markup_accel_tooltip ( + app.get_accels_for_action (ACTION_PREFIX + ACTION_OPTIONS), _("Options") + ), + icon_name = "open-menu" + }; + menu_button.set_popover (app_popover); + // Unable to set markup on Granite.ModeSwitch so fake a Granite acellerator tooltip for now. - mode_switch = new Granite.ModeSwitch.from_icon_name ("edit-symbolic", "head-thinking-symbolic") { + mode_switch = new Granite.ModeSwitch.from_icon_name ( + "edit-symbolic", + "head-thinking-symbolic" + ) { margin_end = 12, margin_start = 12, valign = Gtk.Align.CENTER, @@ -272,7 +313,6 @@ warning ("WITH DEBUGGING"); xalign = 0.5f }; title_label.add_css_class (Granite.STYLE_CLASS_H3_LABEL); - title_label.show (); grade_label = new Gtk.Label ("Easy") { use_markup = true, From e5819eb755422b21c883b6efe367d82b6177955f Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 27 Dec 2023 17:26:03 +0000 Subject: [PATCH 032/142] Move progress and grade label to main grid, narrower header --- src/HeaderBar/ProgressIndicator.vala | 3 +- src/View.vala | 45 ++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/HeaderBar/ProgressIndicator.vala b/src/HeaderBar/ProgressIndicator.vala index f667b7a..cf42978 100644 --- a/src/HeaderBar/ProgressIndicator.vala +++ b/src/HeaderBar/ProgressIndicator.vala @@ -41,14 +41,13 @@ construct { spinner = new Gtk.Spinner (); label = new Gtk.Label (null); - label.add_css_class (Granite.STYLE_CLASS_H3_LABEL); + label.add_css_class (Granite.STYLE_CLASS_H4_LABEL); append (label); append (spinner); cancel_button = new Gtk.Button (); var img = new Gtk.Image.from_icon_name ("process-stop-symbolic") { - icon_size = Gtk.IconSize.LARGE, tooltip_text = _("Cancel solving") }; cancel_button.child = img; diff --git a/src/View.vala b/src/View.vala index f364a71..2088f7c 100644 --- a/src/View.vala +++ b/src/View.vala @@ -320,18 +320,15 @@ warning ("WITH DEBUGGING"); }; grade_label.add_css_class (Granite.STYLE_CLASS_H4_LABEL); - var title_grid = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); - title_grid.append (title_label); - title_grid.append (grade_label); - progress_stack = new Gtk.Stack (); progress_stack.add_named (progress_indicator, "Progress"); - progress_stack.add_named (title_grid, "Title"); - progress_stack.set_visible_child_name ("Title"); + progress_stack.add_named (grade_label, "Grade"); + progress_stack.add_named (new Gtk.Label (""), "None"); + progress_stack.set_visible_child_name ("None"); header_bar = new Gtk.HeaderBar () { show_title_buttons = true, - title_widget = progress_stack, + title_widget = title_label, can_focus = false }; header_bar.add_css_class ("gnonograms-header"); @@ -350,9 +347,18 @@ warning ("WITH DEBUGGING"); set_titlebar (header_bar); - row_clue_box = new ClueBox (Gtk.Orientation.VERTICAL, this); - column_clue_box = new ClueBox (Gtk.Orientation.HORIZONTAL, this); - cell_grid = new CellGrid (this); + row_clue_box = new ClueBox (Gtk.Orientation.VERTICAL, this) { + halign = Gtk.Align.END, + vexpand = false + }; + column_clue_box = new ClueBox (Gtk.Orientation.HORIZONTAL, this) { + valign = Gtk.Align.END, + hexpand = false + }; + cell_grid = new CellGrid (this) { + halign = START, + valign = START + }; var vert_sizegroup = new Gtk.SizeGroup (VERTICAL); vert_sizegroup.add_widget (row_clue_box); @@ -363,14 +369,18 @@ warning ("WITH DEBUGGING"); toast_overlay = new Adw.ToastOverlay () { vexpand = false, - valign = Gtk.Align.START + valign = Gtk.Align.CENTER, + halign = Gtk.Align.CENTER, + child = progress_stack }; main_grid = new Gtk.Grid () { focusable = true, // Needed for key controller to work row_spacing = 0, margin_bottom = margin_end = GRID_BORDER, - column_spacing = GRID_COLUMN_SPACING + column_spacing = GRID_COLUMN_SPACING, + valign = Gtk.Align.START, + halign = Gtk.Align.START }; main_grid.attach (toast_overlay, 0, 0, 1, 1); /* show temporary messages */ main_grid.attach (row_clue_box, 0, 1, 1, 1); /* Clues fordimensions.height*/ @@ -578,8 +588,12 @@ warning ("WITH DEBUGGING"); if (progress_timeout_id > 0) { Source.remove (progress_timeout_id); progress_timeout_id = 0; + } + + if (game_grade != Difficulty.UNDEFINED) { + progress_stack.set_visible_child_name ("Grade"); } else { - progress_stack.set_visible_child_name ("Title"); + progress_stack.set_visible_child_name ("None"); } update_all_labels_completeness (); @@ -610,6 +624,11 @@ warning ("WITH DEBUGGING"); title_label.label = game_name; title_label.tooltip_text = controller.current_game_path; grade_label.label = game_grade.to_string (); + if (game_grade != Difficulty.UNDEFINED) { + progress_stack.set_visible_child_name ("Grade"); + } else { + progress_stack.set_visible_child_name ("None"); + } } private void set_buttons_sensitive (bool sensitive) { From ae23496b17bee37e4982e9527b213a95d826a25a Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sat, 17 Feb 2024 17:23:18 +0000 Subject: [PATCH 033/142] Lose Application.css --- data/Application.css | 28 ---------------------------- data/gresource.xml | 1 - 2 files changed, 29 deletions(-) delete mode 100644 data/Application.css diff --git a/data/Application.css b/data/Application.css deleted file mode 100644 index 66e99e2..0000000 --- a/data/Application.css +++ /dev/null @@ -1,28 +0,0 @@ - /* - * Copyright (C) 2010-2021 Jeremy Wootten - * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: - * Jeremy Wootten - */ - -@define-color GNONOGRAMS_DARK_PURPLE #180297; -@define-color GNONOGRAMS_PALE_PURPLE #cdc9e0; -.gnonograms-header { - border-style: ridge; - border-top-width: 10px; - border-color: @GNONOGRAMS_DARK_PURPLE; -} - diff --git a/data/gresource.xml b/data/gresource.xml index f62b853..d802094 100644 --- a/data/gresource.xml +++ b/data/gresource.xml @@ -3,6 +3,5 @@ icons/24/head-thinking-symbolic.svg icons/24/head-thinking-symbolic.svg - Application.css From a9763981bbd254a8a666ea4514cbcf8132f82896 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sat, 17 Feb 2024 17:38:20 +0000 Subject: [PATCH 034/142] Lose head-thinking icon --- data/gresource.xml | 7 -- data/icons/24/head-thinking-symbolic.svg | 123 ----------------------- meson.build | 6 -- src/View.vala | 2 +- 4 files changed, 1 insertion(+), 137 deletions(-) diff --git a/data/gresource.xml b/data/gresource.xml index d802094..e69de29 100644 --- a/data/gresource.xml +++ b/data/gresource.xml @@ -1,7 +0,0 @@ - - - - icons/24/head-thinking-symbolic.svg - icons/24/head-thinking-symbolic.svg - - diff --git a/data/icons/24/head-thinking-symbolic.svg b/data/icons/24/head-thinking-symbolic.svg index 7c3bbd0..e69de29 100644 --- a/data/icons/24/head-thinking-symbolic.svg +++ b/data/icons/24/head-thinking-symbolic.svg @@ -1,123 +0,0 @@ - - - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/meson.build b/meson.build index 9dfce43..63402a2 100644 --- a/meson.build +++ b/meson.build @@ -11,11 +11,6 @@ if get_option('with_debugging') endif gnome = import('gnome') -gresource = gnome.compile_resources( - 'gresource', - 'data/gresource.xml', - source_dir: 'data' -) config_data = configuration_data() config_data.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), get_option('localedir'))) @@ -30,7 +25,6 @@ config_file = configure_file( executable ( meson.project_name (), - gresource, 'src/Application.vala', 'src/Controller.vala', 'src/View.vala', diff --git a/src/View.vala b/src/View.vala index 2088f7c..6386063 100644 --- a/src/View.vala +++ b/src/View.vala @@ -297,7 +297,7 @@ warning ("WITH DEBUGGING"); // Unable to set markup on Granite.ModeSwitch so fake a Granite acellerator tooltip for now. mode_switch = new Granite.ModeSwitch.from_icon_name ( "edit-symbolic", - "head-thinking-symbolic" + "system-run-symbolic" ) { margin_end = 12, margin_start = 12, From 77a22e0d685844d309b069ee6024594137df99b2 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sat, 17 Feb 2024 18:04:38 +0000 Subject: [PATCH 035/142] Remove residual ref to Application.css --- src/View.vala | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/View.vala b/src/View.vala index 6386063..a459583 100644 --- a/src/View.vala +++ b/src/View.vala @@ -177,15 +177,6 @@ warning ("WITH DEBUGGING"); action_accelerators.set (ACTION_DEBUG_COL, "C"); #endif - try { - var css_provider = new Gtk.CssProvider (); - css_provider.load_from_resource ("com/github/jeremypw/gnonograms/Application.css"); - Gtk.StyleContext.add_provider_for_display ( - Gdk.Display.get_default (), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION - ); - } catch (Error e) { - warning ("Error adding css provider: %s", e.message); - } } construct { From 5c33e7f14d95ec29b1bb00eb317c152410f744ce Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sat, 17 Feb 2024 18:11:19 +0000 Subject: [PATCH 036/142] Increase header button image size --- src/HeaderBar/HeaderButton.vala | 13 ++++++++++--- src/View.vala | 6 +++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/HeaderBar/HeaderButton.vala b/src/HeaderBar/HeaderButton.vala index 2cf330f..a46faa3 100644 --- a/src/HeaderBar/HeaderButton.vala +++ b/src/HeaderBar/HeaderButton.vala @@ -20,16 +20,23 @@ public class Gnonograms.HeaderButton : Gtk.Button { public HeaderButton (string icon_name, string action_name, string text) { Object ( - icon_name: icon_name, action_name: action_name, tooltip_markup: Granite.markup_accel_tooltip ( View.app.get_accels_for_action (action_name), text - ) + ), + valign: Gtk.Align.CENTER ); + + var image = new Gtk.Image.from_icon_name (icon_name) { + pixel_size = 24 + }; + + child = image; + add_css_class ("flat"); } construct { - valign = Gtk.Align.CENTER; + } } diff --git a/src/View.vala b/src/View.vala index a459583..3289804 100644 --- a/src/View.vala +++ b/src/View.vala @@ -277,11 +277,15 @@ warning ("WITH DEBUGGING"); app_popover.title = controller.game_name; }); + var menu_image = new Gtk.Image.from_icon_name ("open-menu") { + pixel_size = 32 + }; menu_button = new Gtk.MenuButton () { tooltip_markup = Granite.markup_accel_tooltip ( app.get_accels_for_action (ACTION_PREFIX + ACTION_OPTIONS), _("Options") ), - icon_name = "open-menu" + child = menu_image, + has_frame = false }; menu_button.set_popover (app_popover); From a45fe74c0c1dfef85cee577168de83c81d6d26a7 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sat, 17 Feb 2024 18:27:09 +0000 Subject: [PATCH 037/142] Use some symbolic icons --- src/View.vala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/View.vala b/src/View.vala index 3289804..f4f5a02 100644 --- a/src/View.vala +++ b/src/View.vala @@ -224,17 +224,17 @@ warning ("WITH DEBUGGING"); _("Save Game to Different File") ); undo_button = new HeaderButton ( - "edit-undo", + "edit-undo-symbolic", ACTION_PREFIX + ACTION_UNDO, _("Undo Last Move") ); redo_button = new HeaderButton ( - "edit-redo", + "edit-redo-symbolic", ACTION_PREFIX + ACTION_REDO, _("Redo Last Move") ); check_correct_button = new HeaderButton ( - "media-seek-backward", + "media-seek-backward-symbolic", ACTION_PREFIX + ACTION_CHECK_ERRORS, _("Check for Errors") ); From 467dc61f91459281c8ffcbcc488f606173a32c16 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sat, 17 Feb 2024 18:27:23 +0000 Subject: [PATCH 038/142] Cleanup --- src/View.vala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/View.vala b/src/View.vala index f4f5a02..698bf92 100644 --- a/src/View.vala +++ b/src/View.vala @@ -194,8 +194,6 @@ warning ("WITH DEBUGGING"); }); } - weak Gtk.IconTheme default_theme = Gtk.IconTheme.get_for_display (Gdk.Display.get_default ()); - default_theme.add_resource_path ("/com/github/jeremypw/gnonograms"); var view_actions = new GLib.SimpleActionGroup (); view_actions.add_action_entries (view_action_entries, this); @@ -289,7 +287,7 @@ warning ("WITH DEBUGGING"); }; menu_button.set_popover (app_popover); - // Unable to set markup on Granite.ModeSwitch so fake a Granite acellerator tooltip for now. + // Unable to set markup on Granite.ModeSwitch so fake a Granite accelerator tooltip for now. mode_switch = new Granite.ModeSwitch.from_icon_name ( "edit-symbolic", "system-run-symbolic" From eb8024e67db05939e046b5ecae0adbb0a7782b91 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sat, 17 Feb 2024 18:27:49 +0000 Subject: [PATCH 039/142] Increase fallback cell size --- src/Controller.vala | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index be74296..40378e5 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -222,30 +222,26 @@ public class Gnonograms.Controller : GLib.Object { } private void restore_settings () { + view.cell_size = 48; + current_game_path = ""; if (saved_state != null) { int cell_size = saved_state.get_int ("cell-size"); current_game_path = saved_state.get_string ("current-game-path"); if (cell_size > 0) { view.cell_size = cell_size; } - } else { - /* Error normally thrown running uninstalled */ - /* Default puzzle parameters */ - view.cell_size = 24; - current_game_path = ""; } restore_dimensions (); } private void restore_dimensions () { + dimensions = { 15, 10 }; /* Fallback dimensions */ if (settings != null) { dimensions = { settings.get_uint ("columns").clamp (10, 50), settings.get_uint ("rows").clamp (10, 50) }; - } else { - dimensions = { 15, 10 }; /* Fallback dimensions */ } } From 9a2e1ffa9eb30b3ddfee1088eafdc469ef1f1687 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 19 Feb 2024 18:23:55 +0000 Subject: [PATCH 040/142] Load some colors from settings --- com.github.jeremypw.gnonograms.yml | 2 +- ...com.github.jeremypw.gnonograms.gschema.xml | 18 ++++++++++ libcore/widgets/Cellgrid.vala | 33 ++++++++++++------- src/Application.vala | 9 ++++- src/Controller.vala | 14 -------- 5 files changed, 49 insertions(+), 27 deletions(-) diff --git a/com.github.jeremypw.gnonograms.yml b/com.github.jeremypw.gnonograms.yml index 9929f1f..4174e72 100644 --- a/com.github.jeremypw.gnonograms.yml +++ b/com.github.jeremypw.gnonograms.yml @@ -1,6 +1,6 @@ app-id: com.github.jeremypw.gnonograms runtime: io.elementary.Platform -runtime-version: '7' +runtime-version: 'daily' sdk: io.elementary.Sdk command: com.github.jeremypw.gnonograms finish-args: diff --git a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml index caf81cb..bfc6879 100644 --- a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml +++ b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml @@ -52,6 +52,24 @@ Fade clue if corresponding region is completed without definite error. + + + "#180297" + Color of filled cell when solving + + The color used to mark a filled cell when solving a gnonogram puzzle. + Defaults to Gnonograms Purple. + + + + + "#ffff00" + Color of empty cell when solving + + The color used to mark a empty cell when solving a gnonogram puzzle. + Defaults to yellow + + diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index b05a977..eb9d7be 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -92,7 +92,18 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { colors = new Gdk.RGBA[2, 3]; grid_color.parse (Gnonograms.GRID_COLOR); cell_pattern_type = CellPatternType.CELL; - set_colors (); + // Set default colors + set_colors_for_state ( + GameState.SETTING, + Gnonograms.SETTING_FILLED_COLOR, + Gnonograms.SETTING_EMPTY_COLOR + ); + + set_colors_for_state ( + GameState.SOLVING, + Gnonograms.settings.get_string ("filled-color"), + Gnonograms.settings.get_string ("empty-color") + ); var motion_controller = new Gtk.EventControllerMotion (); add_controller (motion_controller); @@ -138,16 +149,16 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { size_updated (); } - private void set_colors () { - int setting = (int)GameState.SETTING; - colors[setting, (int)CellState.UNKNOWN].parse (Gnonograms.UNKNOWN_COLOR); - colors[setting, (int)CellState.EMPTY].parse (Gnonograms.SETTING_EMPTY_COLOR); - colors[setting, (int)CellState.FILLED].parse (Gnonograms.SETTING_FILLED_COLOR); - - int solving = (int)GameState.SOLVING; - colors[solving, (int)CellState.UNKNOWN].parse (Gnonograms.UNKNOWN_COLOR); - colors[solving, (int)CellState.EMPTY].parse (Gnonograms.SOLVING_EMPTY_COLOR); - colors[solving, (int)CellState.FILLED].parse (Gnonograms.SOLVING_FILLED_COLOR); + public void set_colors_for_state ( + GameState gs, + string filled_color, + string empty_color, + string unknown_color = Gnonograms.UNKNOWN_COLOR + ) { + var setting = (int) gs; + colors[setting, (int) CellState.UNKNOWN].parse (unknown_color); + colors[setting, (int) CellState.EMPTY].parse (empty_color); + colors[setting, (int) CellState.FILLED].parse (filled_color); } private void size_updated () { diff --git a/src/Application.vala b/src/Application.vala index b0b22bd..f62f157 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -16,8 +16,11 @@ * * Author: Jeremy Wootten */ +namespace Gnonograms { + public GLib.Settings saved_state; + public GLib.Settings settings; -public class Gnonograms.App : Gtk.Application { +public class App : Gtk.Application { private Controller controller; public App () { @@ -33,6 +36,9 @@ public class Gnonograms.App : Gtk.Application { GLib.Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8"); GLib.Intl.textdomain (Config.GETTEXT_PACKAGE); + saved_state = new GLib.Settings (Config.APP_ID + ".saved-state"); + settings = new GLib.Settings (Config.APP_ID + ".settings"); + SimpleAction quit_action = new SimpleAction ("quit", null); quit_action.activate.connect (() => { if (controller != null) { @@ -93,3 +99,4 @@ public static int main (string[] args) { var app = new Gnonograms.App (); return app.run (args); } +} diff --git a/src/Controller.vala b/src/Controller.vala index 40378e5..2385bb5 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -20,9 +20,6 @@ public class Gnonograms.Controller : GLib.Object { public signal void quit_app (); - private const string SETTINGS_SCHEMA_ID = "com.github.jeremypw.gnonograms.settings"; - private const string SAVED_STATE_SCHEMA_ID = "com.github.jeremypw.gnonograms.saved-state"; - public Gtk.Window window { get { return (Gtk.Window)view;}} public GameState game_state { get; set; } public Dimensions dimensions { get; set; } @@ -38,8 +35,6 @@ public class Gnonograms.Controller : GLib.Object { private Model model; private Solver? solver; private SimpleRandomGameGenerator? generator; - private GLib.Settings? settings = null; - private GLib.Settings? saved_state = null; private Gnonograms.History history; public string current_game_path { get; private set; default = ""; } private string saved_games_folder; @@ -74,15 +69,6 @@ public class Gnonograms.Controller : GLib.Object { view.update_title (); }); - if (SettingsSchemaSource.get_default ().lookup (SETTINGS_SCHEMA_ID, true) != null && - SettingsSchemaSource.get_default ().lookup (SAVED_STATE_SCHEMA_ID, true) != null) { - - settings = new Settings (SETTINGS_SCHEMA_ID); - saved_state = new Settings (SAVED_STATE_SCHEMA_ID); - } else { - warning ("not found one of the schemas"); - } - var data_home_folder_current = Path.build_path ( Path.DIR_SEPARATOR_S, Environment.get_user_config_dir (), From d603765c13cebcd2f9f7c554676fb755f9d771fc Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 19 Feb 2024 19:42:40 +0000 Subject: [PATCH 041/142] Add color dialog buttos to app popover --- libcore/widgets/Cellgrid.vala | 47 +++++++++++++++-------------------- src/HeaderBar/AppPopover.vala | 36 +++++++++++++++++++++++++++ src/View.vala | 10 ++++++++ 3 files changed, 66 insertions(+), 27 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index eb9d7be..87c0e49 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -92,18 +92,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { colors = new Gdk.RGBA[2, 3]; grid_color.parse (Gnonograms.GRID_COLOR); cell_pattern_type = CellPatternType.CELL; - // Set default colors - set_colors_for_state ( - GameState.SETTING, - Gnonograms.SETTING_FILLED_COLOR, - Gnonograms.SETTING_EMPTY_COLOR - ); - - set_colors_for_state ( - GameState.SOLVING, - Gnonograms.settings.get_string ("filled-color"), - Gnonograms.settings.get_string ("empty-color") - ); + set_colors (); var motion_controller = new Gtk.EventControllerMotion (); add_controller (motion_controller); @@ -132,11 +121,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { view.notify["cell-size"].connect (size_updated); view.controller.notify["dimensions"].connect (size_updated); view.controller.notify["game-state"].connect (() => { - var gs = view.controller.game_state; - unknown_color = colors[(int)gs, (int)CellState.UNKNOWN]; - fill_color = colors[(int)gs, (int)CellState.FILLED]; - empty_color = colors[(int)gs, (int)CellState.EMPTY]; - cell_pattern_type = CellPatternType.UNDEFINED; /* Causes refresh of existing pattern */ + on_game_state_changed (); }); view.model.changed.connect (() => { @@ -149,16 +134,24 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { size_updated (); } - public void set_colors_for_state ( - GameState gs, - string filled_color, - string empty_color, - string unknown_color = Gnonograms.UNKNOWN_COLOR - ) { - var setting = (int) gs; - colors[setting, (int) CellState.UNKNOWN].parse (unknown_color); - colors[setting, (int) CellState.EMPTY].parse (empty_color); - colors[setting, (int) CellState.FILLED].parse (filled_color); + public void set_colors () { + var setting = (int) GameState.SETTING; + colors[setting, (int) CellState.UNKNOWN].parse (Gnonograms.UNKNOWN_COLOR); + colors[setting, (int) CellState.EMPTY].parse (Gnonograms.SETTING_EMPTY_COLOR); + colors[setting, (int) CellState.FILLED].parse (Gnonograms.SETTING_FILLED_COLOR); + setting = (int) GameState.SOLVING; + colors[setting, (int) CellState.UNKNOWN].parse (Gnonograms.UNKNOWN_COLOR); + colors[setting, (int) CellState.EMPTY].parse (settings.get_string ("empty-color")); + colors[setting, (int) CellState.FILLED].parse (settings.get_string ("filled-color")); + on_game_state_changed (); + } + + private void on_game_state_changed () { + var gs = view.controller.game_state; + unknown_color = colors[(int)gs, (int)CellState.UNKNOWN]; + fill_color = colors[(int)gs, (int)CellState.FILLED]; + empty_color = colors[(int)gs, (int)CellState.EMPTY]; + cell_pattern_type = CellPatternType.UNDEFINED; /* Causes refresh of existing pattern */ } private void size_updated () { diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 16f6f35..5b14637 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -23,6 +23,8 @@ public class Gnonograms.AppPopover : Gtk.Popover { private Gtk.SpinButton row_setting; private Gtk.SpinButton column_setting; private Gtk.Entry title_setting; + private Gtk.ColorDialogButton filled_color_setting; + private Gtk.ColorDialogButton empty_color_setting; public Difficulty grade { get { @@ -64,7 +66,34 @@ public class Gnonograms.AppPopover : Gtk.Popover { } } + public string filled_color { + owned get { + return filled_color_setting.get_rgba ().to_string (); + } + + set { + Gdk.RGBA color = {}; + if (color.parse (value)) { + filled_color_setting.set_rgba (color); + } + } + } + + public string empty_color { + owned get { + return empty_color_setting.get_rgba ().to_string (); + } + + set { + Gdk.RGBA color = {}; + if (color.parse (value)) { + empty_color_setting.set_rgba (color); + } + } + } + construct { + autohide = false; grade_setting = new Gtk.ComboBoxText (); foreach (Difficulty d in Difficulty.all_human ()) { grade_setting.append (((uint)d).to_string (), d.to_string ()); @@ -94,6 +123,9 @@ public class Gnonograms.AppPopover : Gtk.Popover { placeholder_text = _("Enter title of game here") }; + filled_color_setting = new Gtk.ColorDialogButton (new Gtk.ColorDialog ()); + empty_color_setting = new Gtk.ColorDialogButton (new Gtk.ColorDialog ()); + var settings_grid = new Gtk.Grid () { orientation = Gtk.Orientation.VERTICAL, row_spacing = 12, @@ -109,6 +141,10 @@ public class Gnonograms.AppPopover : Gtk.Popover { settings_grid.attach (row_setting, 1, 2, 1); settings_grid.attach (new Gtk.Label (_("Columns:")), 0, 3, 1); settings_grid.attach (column_setting, 1, 3, 1); + settings_grid.attach (new Gtk.Label (_("Filled Color:")), 0, 4, 1); + settings_grid.attach (filled_color_setting, 1, 4, 1); + settings_grid.attach (new Gtk.Label (_("Empty Color:")), 0, 5, 1); + settings_grid.attach (empty_color_setting, 1, 5, 1); var cancel_button = new Gtk.Button.with_label (_("Cancel")); var apply_button = new Gtk.Button.with_label (_("Apply")); diff --git a/src/View.vala b/src/View.vala index 698bf92..91188fd 100644 --- a/src/View.vala +++ b/src/View.vala @@ -267,12 +267,22 @@ warning ("WITH DEBUGGING"); controller.generator_grade = app_popover.grade; controller.dimensions = {app_popover.columns, app_popover.rows}; controller.game_name = app_popover.title; // Must come after changing dimensions + settings.set_string ("filled-color", app_popover.filled_color); + settings.set_string ("empty-color", app_popover.empty_color); + // Wait for settings to update + Idle.add (() => { + cell_grid.set_colors (); + cell_grid.queue_draw (); + return Source.REMOVE; + }); }); app_popover.show.connect (() => { /* Allow parent to set values first */ app_popover.grade = controller.generator_grade; app_popover.rows = controller.dimensions.height; app_popover.columns = controller.dimensions.width; app_popover.title = controller.game_name; + app_popover.filled_color = settings.get_string ("filled-color"); + app_popover.empty_color = settings.get_string ("empty-color"); }); var menu_image = new Gtk.Image.from_icon_name ("open-menu") { From ce0cfc6f3d36c34af32010486982d722d2ad2a44 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 19 Feb 2024 20:01:51 +0000 Subject: [PATCH 042/142] Use rgb() format in schema --- data/schemas/com.github.jeremypw.gnonograms.gschema.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml index bfc6879..d31c26f 100644 --- a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml +++ b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml @@ -54,7 +54,7 @@ - "#180297" + "rgb(24,18,151)" Color of filled cell when solving The color used to mark a filled cell when solving a gnonogram puzzle. @@ -63,7 +63,7 @@ - "#ffff00" + "rgb(255,255,0)" Color of empty cell when solving The color used to mark a empty cell when solving a gnonogram puzzle. From 9f775b2c28f49a4cad298718eb4668cc92adcc3f Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 20 Feb 2024 09:41:58 +0000 Subject: [PATCH 043/142] Tweak alignments --- src/View.vala | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/View.vala b/src/View.vala index 91188fd..f1c5c7e 100644 --- a/src/View.vala +++ b/src/View.vala @@ -352,11 +352,9 @@ warning ("WITH DEBUGGING"); row_clue_box = new ClueBox (Gtk.Orientation.VERTICAL, this) { halign = Gtk.Align.END, - vexpand = false }; column_clue_box = new ClueBox (Gtk.Orientation.HORIZONTAL, this) { valign = Gtk.Align.END, - hexpand = false }; cell_grid = new CellGrid (this) { halign = START, @@ -371,7 +369,6 @@ warning ("WITH DEBUGGING"); horiz_sizegroup.add_widget (cell_grid); toast_overlay = new Adw.ToastOverlay () { - vexpand = false, valign = Gtk.Align.CENTER, halign = Gtk.Align.CENTER, child = progress_stack @@ -380,13 +377,12 @@ warning ("WITH DEBUGGING"); main_grid = new Gtk.Grid () { focusable = true, // Needed for key controller to work row_spacing = 0, - margin_bottom = margin_end = GRID_BORDER, column_spacing = GRID_COLUMN_SPACING, - valign = Gtk.Align.START, - halign = Gtk.Align.START + valign = Gtk.Align.END, + halign = Gtk.Align.END, }; main_grid.attach (toast_overlay, 0, 0, 1, 1); /* show temporary messages */ - main_grid.attach (row_clue_box, 0, 1, 1, 1); /* Clues fordimensions.height*/ + main_grid.attach (row_clue_box, 0, 1, 1, 1); /* Clues for dimensions.height*/ main_grid.attach (column_clue_box, 1, 0, 1, 1); /* Clues for columns */ main_grid.attach (cell_grid, 1, 1, 1, 1); @@ -794,6 +790,7 @@ warning ("WITH DEBUGGING"); } else { cell_size -= (int)delta; //FIXME This is a hack to fix redrawing the window when the grid gets smaller. For some + //FIXME This is a hack to fix redrawing the window when the grid gets smaller. For some //reason the window only properly redraws when the size increases. Review for later versions of Gtk4/Elementary cell_size -= (int)delta; Idle.add (() => { From 7b5fd08d0099c71003367701d24084b5178331c9 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 20 Feb 2024 09:52:40 +0000 Subject: [PATCH 044/142] Move progress stack back into header --- src/View.vala | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/View.vala b/src/View.vala index f1c5c7e..37b581c 100644 --- a/src/View.vala +++ b/src/View.vala @@ -323,15 +323,16 @@ warning ("WITH DEBUGGING"); }; grade_label.add_css_class (Granite.STYLE_CLASS_H4_LABEL); - progress_stack = new Gtk.Stack (); + progress_stack = new Gtk.Stack () { + halign = Gtk.Align.CENTER, + }; progress_stack.add_named (progress_indicator, "Progress"); - progress_stack.add_named (grade_label, "Grade"); - progress_stack.add_named (new Gtk.Label (""), "None"); - progress_stack.set_visible_child_name ("None"); + progress_stack.add_named (title_label, "Title"); + progress_stack.set_visible_child_name ("Title"); header_bar = new Gtk.HeaderBar () { show_title_buttons = true, - title_widget = title_label, + title_widget = progress_stack, can_focus = false }; header_bar.add_css_class ("gnonograms-header"); @@ -370,8 +371,7 @@ warning ("WITH DEBUGGING"); toast_overlay = new Adw.ToastOverlay () { valign = Gtk.Align.CENTER, - halign = Gtk.Align.CENTER, - child = progress_stack + halign = Gtk.Align.CENTER }; main_grid = new Gtk.Grid () { @@ -387,7 +387,8 @@ warning ("WITH DEBUGGING"); main_grid.attach (cell_grid, 1, 1, 1, 1); var scroll_controller = new Gtk.EventControllerScroll ( - Gtk.EventControllerScrollFlags.VERTICAL | Gtk.EventControllerScrollFlags.DISCRETE + Gtk.EventControllerScrollFlags.VERTICAL | + Gtk.EventControllerScrollFlags.DISCRETE ); main_grid.add_controller (scroll_controller); From 171932cd69f02bc7253016f900bbdc90770c5f9b Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 20 Feb 2024 10:23:34 +0000 Subject: [PATCH 045/142] View: Reduce line lengths --- src/View.vala | 98 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 31 deletions(-) diff --git a/src/View.vala b/src/View.vala index 37b581c..d0a9491 100644 --- a/src/View.vala +++ b/src/View.vala @@ -139,9 +139,15 @@ public class Gnonograms.View : Gtk.ApplicationWindow { static construct { app = (Gtk.Application)(Application.get_default ()); #if WITH_DEBUGGING -warning ("WITH DEBUGGING"); - view_action_entries += ActionEntry () { name = ACTION_DEBUG_ROW, activate = action_debug_row }; - view_action_entries += ActionEntry () { name = ACTION_DEBUG_COL, activate = action_debug_col }; + warning ("WITH DEBUGGING"); + view_action_entries += ActionEntry () { + name = ACTION_DEBUG_ROW, + activate = action_debug_row + }; + view_action_entries += ActionEntry () { + name = ACTION_DEBUG_COL, + activate = action_debug_col + }; #endif action_accelerators.set (ACTION_UNDO, "Z"); action_accelerators.set (ACTION_REDO, "Z"); @@ -181,19 +187,15 @@ warning ("WITH DEBUGGING"); construct { resizable = false; + var granite_settings = Granite.Settings.get_default (); var gtk_settings = Gtk.Settings.get_default (); - - if (gtk_settings != null && granite_settings != null) { - gtk_settings.gtk_application_prefer_dark_theme = - granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; - - granite_settings.notify["prefers-color-scheme"].connect (() => { - gtk_settings.gtk_application_prefer_dark_theme = - granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; - }); - } - + var prefer_dark = granite_settings.prefers_color_scheme == DARK; + gtk_settings.gtk_application_prefer_dark_theme = prefer_dark; + granite_settings.notify["prefers-color-scheme"].connect (() => { + prefer_dark = granite_settings.prefers_color_scheme == DARK; + gtk_settings.gtk_application_prefer_dark_theme = prefer_dark; + }); var view_actions = new GLib.SimpleActionGroup (); view_actions.add_action_entries (view_action_entries, this); @@ -290,7 +292,9 @@ warning ("WITH DEBUGGING"); }; menu_button = new Gtk.MenuButton () { tooltip_markup = Granite.markup_accel_tooltip ( - app.get_accels_for_action (ACTION_PREFIX + ACTION_OPTIONS), _("Options") + app.get_accels_for_action ( + ACTION_PREFIX + ACTION_OPTIONS), + _("Options") ), child = menu_image, has_frame = false @@ -458,9 +462,11 @@ warning ("WITH DEBUGGING"); }); notify["can-go-back"].connect (() => { - check_correct_button.sensitive = can_go_back && controller.game_state == GameState.SOLVING; + check_correct_button.sensitive = can_go_back && + controller.game_state == GameState.SOLVING; undo_button.sensitive = can_go_back; - restart_destructive |= can_go_back; /* May be destructive even if no history (e.g. after automatic solve) */ + /* May be destructive even if no history (e.g. after automatic solve) */ + restart_destructive |= can_go_back; }); notify["can-go-forward"].connect (() => { @@ -491,10 +497,16 @@ warning ("WITH DEBUGGING"); cell_grid.start_drawing.connect ((button, state, double_click) => { if (double_click || button == Gdk.BUTTON_MIDDLE) { - drawing_with_state = controller.game_state == SOLVING ? CellState.UNKNOWN : CellState.EMPTY; + if (controller.game_state == SOLVING) { + drawing_with_state = CellState.UNKNOWN; + } else { + drawing_with_state = CellState.EMPTY; + } } else { + var shift = (state & Gdk.ModifierType.SHIFT_MASK) > 0; if (button == Gdk.BUTTON_SECONDARY || - ((state & Gdk.ModifierType.SHIFT_MASK) > 0) && button == Gdk.BUTTON_PRIMARY) { + button == Gdk.BUTTON_PRIMARY && shift) { + drawing_with_state = CellState.EMPTY; } else { drawing_with_state = CellState.FILLED; @@ -526,10 +538,17 @@ warning ("WITH DEBUGGING"); var available_screen_width = monitor_area.width * 0.9 - GRID_BORDER - GRID_COLUMN_SPACING; var max_cell_width = available_screen_width / (n_cols * (1.3)); - var available_grid_height = (int)(surface.get_height () - header_bar.get_allocated_height () - GRID_BORDER); + var available_grid_height = + (int)(surface.get_height () - + header_bar.get_allocated_height () - + GRID_BORDER); + var opt_cell_height = (int)(available_grid_height / (n_rows * (1.4))); + var available_screen_height = + monitor_area.height * 0.9 - + header_bar.get_allocated_height () - + GRID_BORDER; - var available_screen_height = monitor_area.height * 0.9 - header_bar.get_allocated_height () - GRID_BORDER; var max_cell_height = available_screen_height / (n_rows * (1.4)); var max_cell_size = (int)(double.min (max_cell_width, max_cell_height)); @@ -640,7 +659,11 @@ warning ("WITH DEBUGGING"); restart_destructive = sensitive && !model.is_blank (controller.game_state); undo_button.sensitive = sensitive && can_go_back; redo_button.sensitive = sensitive && can_go_forward; - check_correct_button.sensitive = sensitive && controller.game_state == GameState.SOLVING && can_go_back; + check_correct_button.sensitive = + sensitive && + controller.game_state == GameState.SOLVING && + can_go_back; + hint_button.sensitive = sensitive && controller.game_state == GameState.SOLVING; auto_solve_button.sensitive = sensitive; } @@ -673,7 +696,10 @@ warning ("WITH DEBUGGING"); } } - private void make_move_at_cell (CellState state = drawing_with_state, Cell target = current_cell) { + private void make_move_at_cell ( + CellState state = drawing_with_state, + Cell target = current_cell + ) { if (target == NULL_CELL) { return; } @@ -697,8 +723,14 @@ warning ("WITH DEBUGGING"); var col = current_cell.col; if (controller.game_state == GameState.SETTING) { - row_clue_box.update_clue_text (row, model.get_label_text_from_solution (row, false)); - column_clue_box.update_clue_text (col, model.get_label_text_from_solution (col, true)); + row_clue_box.update_clue_text ( + row, + model.get_label_text_from_solution (row, false) + ); + column_clue_box.update_clue_text ( + col, + model.get_label_text_from_solution (col, true) + ); } else { update_clue_complete (row, false); update_clue_complete (col, true); @@ -714,12 +746,16 @@ warning ("WITH DEBUGGING"); private uint progress_timeout_id = 0; private void schedule_show_progress (Cancellable cancellable) { - progress_timeout_id = Timeout.add_full (Priority.HIGH_IDLE, PROGRESS_DELAY_MSEC, () => { - progress_indicator.cancellable = cancellable; - progress_stack.set_visible_child_name ("Progress"); - progress_timeout_id = 0; - return false; - }); + progress_timeout_id = Timeout.add_full ( + Priority.HIGH_IDLE, + PROGRESS_DELAY_MSEC, + () => { + progress_indicator.cancellable = cancellable; + progress_stack.set_visible_child_name ("Progress"); + progress_timeout_id = 0; + return false; + } + ); } private void stop_painting () { From cbe4bfd9df8ab8a2391ad1209fb18cfe595336ab Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 20 Feb 2024 10:30:39 +0000 Subject: [PATCH 046/142] Controller: reduce line lengths --- src/Controller.vala | 38 +++++++++++++++++++++++++++++++------- src/View.vala | 44 ++++++++++++++++++++++---------------------- 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index 2385bb5..c9edd56 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -103,11 +103,28 @@ public class Gnonograms.Controller : GLib.Object { view.present (); - bind_property ("generator-grade", view, "generator-grade", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); - bind_property ("is-readonly", view, "readonly", BindingFlags.SYNC_CREATE); + bind_property ( + "generator-grade", + view, "generator-grade", + BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL + ); + bind_property ( + "is-readonly", + view, "readonly", + BindingFlags.SYNC_CREATE + ); - history.bind_property ("can-go-back", view, "can-go-back", BindingFlags.SYNC_CREATE); - history.bind_property ("can-go-forward", view, "can-go-forward", BindingFlags.SYNC_CREATE); + history.bind_property ( + "can-go-back", + view, + "can-go-back", + BindingFlags.SYNC_CREATE + ); + history.bind_property ( + "can-go-forward", + view, "can-go-forward", + BindingFlags.SYNC_CREATE + ); restore_game.begin ((obj, res) => { if (!restore_game.end (res)) { @@ -301,7 +318,11 @@ public class Gnonograms.Controller : GLib.Object { game_state = GameState.UNDEFINED; clear_history (); try { - reader = new Filereader (window, Environment.get_user_special_dir (UserDirectory.DOCUMENTS), game); + reader = new Filereader ( + window, + Environment.get_user_special_dir (UserDirectory.DOCUMENTS), + game + ); } catch (GLib.Error e) { if (!(e is IOError.CANCELLED)) { var basename = game != null ? game.get_basename () : _("game"); @@ -450,7 +471,7 @@ public class Gnonograms.Controller : GLib.Object { /* Check if puzzle finished */ if (game_state == GameState.SOLVING && !model.solution_is_blank () && model.is_finished) { if (model.count_errors () == 0) { - ///TRANSLATORS: "Correct" is used as an adjective, indicating that a correct (valid) solution has been found. +///TRANSLATORS: "Correct" is used as an adjective, indicating that a correct (valid) solution has been found. view.send_notification (_("Correct solution")); } else if (model.working_matches_clues ()) { view.send_notification (_("Alternative solution found")); @@ -556,7 +577,10 @@ public class Gnonograms.Controller : GLib.Object { } #endif - private async SolverState start_solving (bool copy_to_working = false, bool copy_to_solution = false) { + private async SolverState start_solving ( + bool copy_to_working = false, + bool copy_to_solution = false + ) { /* Try as hard as possible to find solution, regardless of grade setting */ var state = SolverState.UNDEFINED; var cancellable = new Cancellable (); diff --git a/src/View.vala b/src/View.vala index d0a9491..9ab754c 100644 --- a/src/View.vala +++ b/src/View.vala @@ -141,12 +141,12 @@ public class Gnonograms.View : Gtk.ApplicationWindow { #if WITH_DEBUGGING warning ("WITH DEBUGGING"); view_action_entries += ActionEntry () { - name = ACTION_DEBUG_ROW, + name = ACTION_DEBUG_ROW, activate = action_debug_row }; - view_action_entries += ActionEntry () { - name = ACTION_DEBUG_COL, - activate = action_debug_col + view_action_entries += ActionEntry () { + name = ACTION_DEBUG_COL, + activate = action_debug_col }; #endif action_accelerators.set (ACTION_UNDO, "Z"); @@ -274,7 +274,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { // Wait for settings to update Idle.add (() => { cell_grid.set_colors (); - cell_grid.queue_draw (); + cell_grid.queue_draw (); return Source.REMOVE; }); }); @@ -287,13 +287,13 @@ public class Gnonograms.View : Gtk.ApplicationWindow { app_popover.empty_color = settings.get_string ("empty-color"); }); - var menu_image = new Gtk.Image.from_icon_name ("open-menu") { + var menu_image = new Gtk.Image.from_icon_name ("open-menu") { pixel_size = 32 }; menu_button = new Gtk.MenuButton () { tooltip_markup = Granite.markup_accel_tooltip ( app.get_accels_for_action ( - ACTION_PREFIX + ACTION_OPTIONS), + ACTION_PREFIX + ACTION_OPTIONS), _("Options") ), child = menu_image, @@ -466,7 +466,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { controller.game_state == GameState.SOLVING; undo_button.sensitive = can_go_back; /* May be destructive even if no history (e.g. after automatic solve) */ - restart_destructive |= can_go_back; + restart_destructive |= can_go_back; }); notify["can-go-forward"].connect (() => { @@ -538,15 +538,15 @@ public class Gnonograms.View : Gtk.ApplicationWindow { var available_screen_width = monitor_area.width * 0.9 - GRID_BORDER - GRID_COLUMN_SPACING; var max_cell_width = available_screen_width / (n_cols * (1.3)); - var available_grid_height = - (int)(surface.get_height () - - header_bar.get_allocated_height () - + var available_grid_height = + (int)(surface.get_height () - + header_bar.get_allocated_height () - GRID_BORDER); var opt_cell_height = (int)(available_grid_height / (n_rows * (1.4))); - var available_screen_height = - monitor_area.height * 0.9 - - header_bar.get_allocated_height () - + var available_screen_height = + monitor_area.height * 0.9 - + header_bar.get_allocated_height () - GRID_BORDER; var max_cell_height = available_screen_height / (n_rows * (1.4)); @@ -659,9 +659,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { restart_destructive = sensitive && !model.is_blank (controller.game_state); undo_button.sensitive = sensitive && can_go_back; redo_button.sensitive = sensitive && can_go_forward; - check_correct_button.sensitive = - sensitive && - controller.game_state == GameState.SOLVING && + check_correct_button.sensitive = + sensitive && + controller.game_state == GameState.SOLVING && can_go_back; hint_button.sensitive = sensitive && controller.game_state == GameState.SOLVING; @@ -697,7 +697,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } private void make_move_at_cell ( - CellState state = drawing_with_state, + CellState state = drawing_with_state, Cell target = current_cell ) { if (target == NULL_CELL) { @@ -724,11 +724,11 @@ public class Gnonograms.View : Gtk.ApplicationWindow { if (controller.game_state == GameState.SETTING) { row_clue_box.update_clue_text ( - row, + row, model.get_label_text_from_solution (row, false) ); column_clue_box.update_clue_text ( - col, + col, model.get_label_text_from_solution (col, true) ); } else { @@ -747,8 +747,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private uint progress_timeout_id = 0; private void schedule_show_progress (Cancellable cancellable) { progress_timeout_id = Timeout.add_full ( - Priority.HIGH_IDLE, - PROGRESS_DELAY_MSEC, + Priority.HIGH_IDLE, + PROGRESS_DELAY_MSEC, () => { progress_indicator.cancellable = cancellable; progress_stack.set_visible_child_name ("Progress"); From 2bd74e5e56e6d89a6114a2664b898f3e4326d836 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 20 Feb 2024 12:09:06 +0000 Subject: [PATCH 047/142] Fit cell size to resizable window --- ...com.github.jeremypw.gnonograms.gschema.xml | 22 ++++----- src/Controller.vala | 9 ++-- src/View.vala | 49 ++++++------------- 3 files changed, 28 insertions(+), 52 deletions(-) diff --git a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml index d31c26f..3e9347a 100644 --- a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml +++ b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml @@ -81,12 +81,15 @@ - - (0, 0) - Position of the window - - The x and y coordinate of the window origin. - + + 720 + Most recent window height + Most recent window height + + + 1024 + Most recent window width + Most recent window width '' @@ -95,12 +98,5 @@ The location where the current game is stored (if it is not an unsaved game). - - 32 - Size of individual cells - - The width and height, in pixels, of the square cells making up a puzzle grid). - - diff --git a/src/Controller.vala b/src/Controller.vala index c9edd56..ba5c930 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -102,6 +102,11 @@ public class Gnonograms.Controller : GLib.Object { } view.present (); + /* + * This is very finicky. Bind size after present else set_titlebar gives us bad sizes + */ + saved_state.bind ("window-height", view, "default-height", SettingsBindFlags.DEFAULT); + saved_state.bind ("window-width", view, "default-width", SettingsBindFlags.DEFAULT); bind_property ( "generator-grade", @@ -228,11 +233,7 @@ public class Gnonograms.Controller : GLib.Object { view.cell_size = 48; current_game_path = ""; if (saved_state != null) { - int cell_size = saved_state.get_int ("cell-size"); current_game_path = saved_state.get_string ("current-game-path"); - if (cell_size > 0) { - view.cell_size = cell_size; - } } restore_dimensions (); diff --git a/src/View.vala b/src/View.vala index 9ab754c..08b7b24 100644 --- a/src/View.vala +++ b/src/View.vala @@ -186,8 +186,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } construct { - resizable = false; - + set_default_size (900, 700); var granite_settings = Granite.Settings.get_default (); var gtk_settings = Gtk.Settings.get_default (); var prefer_dark = granite_settings.prefers_color_scheme == DARK; @@ -384,6 +383,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { column_spacing = GRID_COLUMN_SPACING, valign = Gtk.Align.END, halign = Gtk.Align.END, + hexpand = true, + vexpand = true }; main_grid.attach (toast_overlay, 0, 0, 1, 1); /* show temporary messages */ main_grid.attach (row_clue_box, 0, 1, 1, 1); /* Clues for dimensions.height*/ @@ -490,7 +491,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { calc_cell_size (); }); - cell_grid.leave.connect (() => { + cell_grid.leave.connect (() => { row_clue_box.unhighlight_all (); column_clue_box.unhighlight_all (); }); @@ -517,46 +518,24 @@ public class Gnonograms.View : Gtk.ApplicationWindow { }); cell_grid.stop_drawing.connect (stop_painting); + notify["default-width"].connect (calc_cell_size); + notify["default-height"].connect (calc_cell_size); } private void calc_cell_size () { - // Update cell-size if required to fit on screen but without changing window size unnecessarily - // The dimensions may have increased or decreased so may need to increase or decrease cell size - // It is assumed up to 90% of the screen area can be used + // Update cell-size if required to fit in window var n_cols = controller.dimensions.width; var n_rows = controller.dimensions.height; - var monitor_area = Gdk.Rectangle () { - width = 1024, - height = 768 - }; - Gdk.Surface? surface = get_surface (); - if (surface != null) { - monitor_area = Utils.get_monitor_area (surface); - } + var grid_width = this.default_width; + int header_height, nat; + header_bar.measure (VERTICAL, grid_width, out header_height, out nat, null, null); + var grid_height = this.default_height - header_height; + var max_cell_width = grid_width / (n_cols * (1.3)); - var available_screen_width = monitor_area.width * 0.9 - GRID_BORDER - GRID_COLUMN_SPACING; - var max_cell_width = available_screen_width / (n_cols * (1.3)); - var available_grid_height = - (int)(surface.get_height () - - header_bar.get_allocated_height () - - GRID_BORDER); - - var opt_cell_height = (int)(available_grid_height / (n_rows * (1.4))); - var available_screen_height = - monitor_area.height * 0.9 - - header_bar.get_allocated_height () - - GRID_BORDER; - - var max_cell_height = available_screen_height / (n_rows * (1.4)); - - var max_cell_size = (int)(double.min (max_cell_width, max_cell_height)); - if (max_cell_size < cell_size) { - cell_size = max_cell_size; - } else if (opt_cell_height > 0 && cell_size < opt_cell_height) { - cell_size = int.min (max_cell_size, opt_cell_height); - } + var max_cell_height = grid_height / (n_rows * (1.4)); + cell_size = (int) (double.min (max_cell_width, max_cell_height)).clamp (8, 128); } public string[] get_clues (bool is_column) { From 0df3ed7d453f75ff1ee3d96eaf29042b93ab4509 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 20 Feb 2024 14:31:06 +0000 Subject: [PATCH 048/142] Tweak font size --- libcore/widgets/Clue.vala | 20 +++++--------------- libcore/widgets/Cluebox.vala | 9 +++------ src/View.vala | 4 ++-- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/libcore/widgets/Clue.vala b/libcore/widgets/Clue.vala index b2cdf55..fc0a6ec 100644 --- a/libcore/widgets/Clue.vala +++ b/libcore/widgets/Clue.vala @@ -56,19 +56,9 @@ class Gnonograms.Clue : Object { text = "0"; - label.realize.connect_after (() => { - _fontsize = (int)((double)cluebox.view.cell_size * 0.4); - update_markup (); - }); - - cluebox.notify["n_cells"].connect (() => { - update_tooltip (); - }); - - cluebox.view.notify["cell-size"].connect (() => { - _fontsize = (int)((double)cluebox.view.cell_size * 0.4); - update_markup (); - }); + label.realize.connect_after (update_markup); + cluebox.notify["n_cells"].connect (update_tooltip); + cluebox.notify["font-size"].connect (update_markup); } public void highlight (bool is_highlight) { @@ -214,12 +204,12 @@ class Gnonograms.Clue : Object { } private void update_markup () { - label.set_markup ("".printf (_fontsize) + get_markup () + ""); + label.set_markup ("".printf (cluebox.font_size) + get_markup () + ""); update_tooltip (); } private void update_tooltip () { - label.set_tooltip_markup ("".printf (_fontsize) + + label.set_tooltip_markup ("".printf (cluebox.font_size) + _("Freedom = %u").printf (cluebox.n_cells - Utils.blockextent_from_clue (_text)) + "" ); diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index b20c5f0..ead9de9 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -19,6 +19,7 @@ public class Gnonograms.ClueBox : Gtk.Box { public unowned View view { get; construct; } + public int font_size { get; private set; } public uint n_cells { get; set; default = 0; } // The number of cells each clue addresses, monitored by clues private Gee.ArrayList clues; @@ -55,18 +56,14 @@ public class Gnonograms.ClueBox : Gtk.Box { append (clue.label); } - set_size (); + // set_size (); }); view.notify["cell-size"].connect (set_size); } private void set_size () { - if (orientation == HORIZONTAL) { - height_request = (int) ((double)(view.controller.dimensions.height * view.cell_size) / 3.0); - } else { - width_request = (int) ((double) (view.controller.dimensions.width * view.cell_size) / 3.0); - } + font_size = (int) ((double) view.cell_size * 0.525); } public string[] get_clue_texts () { diff --git a/src/View.vala b/src/View.vala index 08b7b24..037b19e 100644 --- a/src/View.vala +++ b/src/View.vala @@ -532,9 +532,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { int header_height, nat; header_bar.measure (VERTICAL, grid_width, out header_height, out nat, null, null); var grid_height = this.default_height - header_height; - var max_cell_width = grid_width / (n_cols * (1.3)); + var max_cell_width = grid_width / (n_cols * (1.25)); - var max_cell_height = grid_height / (n_rows * (1.4)); + var max_cell_height = grid_height / (n_rows * (1.35)); cell_size = (int) (double.min (max_cell_width, max_cell_height)).clamp (8, 128); } From 4fe75dff3e337882170d686aa17e5977a7a0ce9a Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 20 Feb 2024 15:25:54 +0000 Subject: [PATCH 049/142] Cleanup, code style, line length --- libcore/Filereader.vala | 2 +- libcore/Filewriter.vala | 10 +- libcore/Model.vala | 19 +- libcore/Region.vala | 281 ++++++++++++--------------- libcore/Solver.vala | 28 +-- libcore/utils.vala | 61 +++--- libcore/widgets/Cluebox.vala | 6 +- src/HeaderBar/HeaderButton.vala | 5 - src/HeaderBar/ProgressIndicator.vala | 1 + src/HeaderBar/RestartButton.vala | 1 - src/View.vala | 71 ++++--- 11 files changed, 250 insertions(+), 235 deletions(-) diff --git a/libcore/Filereader.vala b/libcore/Filereader.vala index 247a6f8..baadb04 100644 --- a/libcore/Filereader.vala +++ b/libcore/Filereader.vala @@ -310,7 +310,7 @@ public class Gnonograms.Filereader : Object { return true; } - /** First four lines of description must be in order @name, @date, @score (difficulty or grade). + /** First four lines of description must be in order @name, @date, @score * Missing data must be represented by blank lines. **/ private bool get_game_description (string? body) { diff --git a/libcore/Filewriter.vala b/libcore/Filewriter.vala index 253e6d2..167ca74 100644 --- a/libcore/Filewriter.vala +++ b/libcore/Filewriter.vala @@ -98,13 +98,15 @@ public class Gnonograms.Filewriter : Object { var file = File.new_for_commandline_arg (game_path); if (file.query_exists () && - !Utils.show_confirm_dialog (_("Overwrite %s").printf (game_path), - _("This action will destroy contents of that file"))) { - + !Utils.show_confirm_dialog ( + _("Overwrite %s").printf (game_path), + _("This action will destroy contents of that file")) + ) { throw new IOError.CANCELLED ("File exists"); } - stream = FileStream.open (game_path, "w"); /* This requires local path, not a uri */ + /* @game_path is local path, not a uri */ + stream = FileStream.open (game_path, "w"); if (stream == null) { throw new IOError.FAILED ("Could not open filestream to %s".printf (game_path)); } diff --git a/libcore/Model.vala b/libcore/Model.vala index 957d110..654cbe8 100644 --- a/libcore/Model.vala +++ b/libcore/Model.vala @@ -64,7 +64,9 @@ public class Gnonograms.Model : GLib.Object { for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) { cs = working_data.get_data_from_rc (r, c); - if (cs != CellState.UNKNOWN && cs != solution_data.get_data_from_rc (r, c)) { + if (cs != CellState.UNKNOWN && + cs != solution_data.get_data_from_rc (r, c) + ) { count++; } } @@ -99,7 +101,10 @@ public class Gnonograms.Model : GLib.Object { public bool is_blank (GameState state) { if (state == GameState.SOLVING) { - return count_state (state, CellState.EMPTY) + count_state (state, CellState.FILLED) == 0; + var non_blank = count_state (state, CellState.EMPTY) + + count_state (state, CellState.FILLED); + + return non_blank == 0; } else { return count_state (state, CellState.FILLED) == 0; } @@ -155,7 +160,10 @@ public class Gnonograms.Model : GLib.Object { return working_data.data2text (idx, length, is_column); } - public Gee.ArrayList get_complete_blocks_from_working (uint index, bool is_column) { + public Gee.ArrayList get_complete_blocks_from_working ( + uint index, + bool is_column + ) { var csa = new CellState[is_column ? rows : cols]; working_data.get_array (index, is_column, ref csa); return Utils.complete_block_array_from_cellstate_array (csa); @@ -201,7 +209,10 @@ public class Gnonograms.Model : GLib.Object { return display_data.get_data_from_rc (r, c); } - private void set_row_data_from_string_array (string[] row_data_strings, My2DCellArray array) { + private void set_row_data_from_string_array ( + string[] row_data_strings, + My2DCellArray array + ) { assert (row_data_strings.length == rows); int row = 0; foreach (var row_string in row_data_strings) { diff --git a/libcore/Region.vala b/libcore/Region.vala index 890442a..8d8abd3 100644 --- a/libcore/Region.vala +++ b/libcore/Region.vala @@ -100,7 +100,6 @@ public class Gnonograms.Region { this.grid = grid; uint max_len = uint.max (grid.rows, grid.cols); uint max_blocks = max_len / 2 + 2; - status = new CellState[max_len]; status_backup = new CellState[max_len]; ranges = new int[max_blocks, 4]; @@ -118,11 +117,9 @@ public class Gnonograms.Region { this.is_column = is_column; this.n_cells = (int)n_cells; this.clue = clue; - temp_status = new CellState[n_cells]; temp_status2 = new CellState[n_cells]; int[] clue_blocks = Utils.block_array_from_clue (clue); - n_blocks = clue_blocks.length; can_be_empty_pointer = n_blocks; //flag for cell that may be empty is_finished_pointer = n_blocks + 1; //flag for finished cell (filled or empty?) @@ -217,10 +214,8 @@ public class Gnonograms.Region { * */ message = ""; in_error = false; - // Get external changes get_status (); - //Is complete or has a (invalid) change been made by another region if (in_error) { return false; @@ -494,7 +489,12 @@ public class Gnonograms.Region { while (current_index < n_cells) { //find a filled sub -region start_is_capped = false; end_is_capped = false; - current_index = seek_next_required_status (CellState.FILLED, current_index, n_cells, 1); + current_index = seek_next_required_status ( + CellState.FILLED, + current_index, + n_cells, + 1 + ); if (current_index == n_cells) { break; @@ -510,9 +510,14 @@ public class Gnonograms.Region { start_is_capped = true; //edge cell } - length = count_consecutive_with_state_from (CellState.FILLED, current_index, true); //current_index not changed - int lastcell = current_index + length - 1; //last filled cell in this (partial) block + length = count_consecutive_with_state_from ( + CellState.FILLED, + current_index, + true //current_index not changed + ); + // @lastcell: last filled cell in this (partial) block + int lastcell = current_index + length - 1; if (lastcell == n_cells - 1 || status[lastcell + 1] == CellState.EMPTY) { end_is_capped = true; //last cell is at edge } @@ -524,8 +529,8 @@ public class Gnonograms.Region { continue; } else { //find largest possible owner of this (partial) block int largest = find_largest_possible_block_for_cell (current_index); - - if (largest == length) {//there is **at least one** largest block that fits exactly. + //Test if there is **at least one** largest block that fits exactly + if (largest == length) { // this region must therefore be complete assign_and_cap_range (current_index, length); current_index += length + 1; @@ -601,7 +606,8 @@ public class Gnonograms.Region { } } - current_index += length; //move past block - if reaches here no operations have been performed on block + //move past block - no operations have been performed on block + current_index += length; } return changed; @@ -621,7 +627,8 @@ public class Gnonograms.Region { continue; //is following cell empty? } - if (get_sole_owner (idx) < 0) { // if owner ambiguous, can only deal with single cell gap + // if owner ambiguous, can only deal with single cell gap + if (get_sole_owner (idx) < 0) { // see if single cell gap which can be marked empty because // to fill it would create a block larger than any permissible. if (status[idx + 2] != CellState.FILLED) { @@ -629,15 +636,21 @@ public class Gnonograms.Region { } // we have found a one cell gap // calculate total length if gap were to be FILLED. - int block_length = count_consecutive_with_state_from (CellState.FILLED, idx + 2, true) + - count_consecutive_with_state_from (CellState.FILLED, idx, false) + 1; + int block_length = ( + count_consecutive_with_state_from ( + CellState.FILLED, idx + 2, true + ) + + count_consecutive_with_state_from ( + CellState.FILLED, idx, false + ) + 1 + ); bool must_be_empty = true; //look for a possible owner at least as long as combined length for (int bl = 0; bl < n_blocks; bl++) { - - if (tags[idx, bl] && blocks[bl] >= block_length) { //possible owner found - gap could be filled + //If possible owner found - gap could be filled + if (tags[idx, bl] && blocks[bl] >= block_length) { must_be_empty = false; break; } @@ -692,13 +705,11 @@ public class Gnonograms.Region { total_count = count_to_the_left + count_to_the_right; if (total_count == 2) { - for (int i = 0; i < n_blocks; i++) { - if (tags[ptr, i] && blocks[i] <= length_to_the_right) { - if (tags[idx, i] && blocks[i] <= length_to_the_left) { - must_not_be_empty = true; // only one block fits in both sides + // only one block fits in both sides + must_not_be_empty = true; } } } @@ -713,7 +724,8 @@ public class Gnonograms.Region { } idx += 2; //skip gap - } else { //only one possible owner of first FILLED cell + } else { + //only one possible owner of first FILLED cell int cell1 = idx; //start of gap idx++; @@ -727,7 +739,9 @@ public class Gnonograms.Region { } else { //if start and end of gap have same owner, fill in the gap. int owner = have_same_owner (cell1, idx); if (owner >= 0) { - changed = set_range_owner (owner, cell1, idx - cell1 + 1, true, false) || changed; + changed = changed || set_range_owner ( + owner, cell1, idx - cell1 + 1, true, false + ); } idx--; @@ -765,7 +779,8 @@ public class Gnonograms.Region { } int s = idx; //first cell with block i as possible owner - int l = count_contiguous_with_same_owner_from (i, idx); //length of contiguous cells having this block (i) as a possible owner. + //length of contiguous cells having this block (i) as a possible owner. + int l = count_contiguous_with_same_owner_from (i, idx); if (l < blocks[i]) { remove_block_from_range (i, s, l, 1); //block cannot be here @@ -801,7 +816,6 @@ public class Gnonograms.Region { int end = start + length - 1; for (int i = 0; i < n_blocks; i++) { - if (completed_blocks[i]) { continue; } @@ -845,7 +859,6 @@ public class Gnonograms.Region { //remove as possible owner blocks between first and last that are wrong length for (int i = first + 1; i < last; i++) { - if (blocks[i] == length) { continue; } @@ -977,12 +990,18 @@ public class Gnonograms.Region { //current_index points to cell on the edge if (status[current_index] == CellState.FILLED) { //first cell is FILLED. Can complete whole block - return set_block_complete_and_cap (current_block_number, current_index, direction); + return set_block_complete_and_cap ( + current_block_number, + current_index, + direction + ); } else { // see if filled cell in range of first block and complete after that int start_of_edge = current_index; int start_of_filling = -1; int block_length = blocks[current_block_number]; - int blocklimit = (dir? current_index + block_length : current_index - block_length); + int blocklimit = dir? + current_index + block_length : + current_index - block_length; if (blocklimit < -1 && blocklimit > n_cells) { in_error = true; @@ -990,7 +1009,12 @@ public class Gnonograms.Region { return false; } - current_index = seek_next_required_status (CellState.FILLED, current_index, blocklimit, direction); + current_index = seek_next_required_status ( + CellState.FILLED, + current_index, + blocklimit, + direction + ); if (current_index != blocklimit) { start_of_filling = current_index; @@ -1014,7 +1038,9 @@ public class Gnonograms.Region { // an unfilled cell found. FILL cells beyond first FILLED cells. // remove block from out of range of first filled cell. - while (current_index != blocklimit && status[current_index] == CellState.FILLED) { + while (current_index != blocklimit && + status[current_index] == CellState.FILLED + ) { set_cell_owner (current_index, current_block_number, true, false); set_cell_empty (start_of_edge); changed = true; @@ -1034,7 +1060,11 @@ public class Gnonograms.Region { current_index = start_of_filling + (dir ? block_length : -block_length); if (current_index >= 0 && current_index < n_cells) { - remove_block_from_cell_to_end (current_block_number, current_index, direction); + remove_block_from_cell_to_end ( + current_block_number, + current_index, + direction + ); } } @@ -1050,7 +1080,6 @@ public class Gnonograms.Region { //starting point is set in current_index and current_block_number before calling. bool dir = (direction == FORWARDS); int loop_step = dir ? 1 : -1; - for (int i = current_index; (i >= 0 && i < n_cells); i += loop_step) { if (status[i] == CellState.EMPTY) { continue; @@ -1058,9 +1087,13 @@ public class Gnonograms.Region { //now pointing at first cell of filled or unknown block after edge if (tags[i, is_finished_pointer]) { //skip to end of finished block - i += (dir ? blocks[current_block_number] - 1 : 1 - blocks[current_block_number]); + i += (dir ? + blocks[current_block_number] - 1 : + 1 - blocks[current_block_number] + ); //now pointing at last cell of filled block - var next_block = current_block_number + loop_step; //Increment or decrement current block as appropriate + var next_block = current_block_number + loop_step; + //Increment or decrement current block as appropriate if (next_block >= 0 || next_block < n_blocks - 1) { current_block_number = next_block; } else { @@ -1079,20 +1112,21 @@ public class Gnonograms.Region { // blocks may have been marked completed - thereby reducing available ranges int[] available_blocks = get_blocks_available (); int bl = available_blocks.length; - if (bl == 0) { return false; } - //update ranges with currently available ranges (can contain only unknown and incomplete cells) + // update ranges with currently available ranges + // (can contain only unknown and incomplete cells) int n_available_ranges = count_available_ranges (false); if (n_available_ranges == 0) { return false; } - int[,] block_start = new int[bl, 2]; //range number and offset of earliest start point - int[,] block_end = new int[bl, 2]; //range number and offset of latest end point - + //range number and offset of earliest start point + int[,] block_start = new int[bl, 2]; + //range number and offset of latest end point + int[,] block_end = new int[bl, 2]; //find earliest start point of each block (treating ranges as all unknown cells) int rng = 0; int offset = 0; @@ -1102,11 +1136,9 @@ public class Gnonograms.Region { for (int b = 0; b < bl; b++) {//for each available block length = blocks[available_blocks[b]]; //get its length - if (ranges[rng, 1] < (length + offset)) {//cannot fit in current range rng++; offset = 0; //skip to start of next range - while (rng < n_available_ranges && ranges[rng, 1] < length) { rng++; //keep skipping if too small } @@ -1119,7 +1151,6 @@ public class Gnonograms.Region { //look for collision with filled cell ptr = ranges[rng, 0] + offset + length; //cell after end of block start = ptr; - while (ptr < n_cells && !tags[ptr, can_be_empty_pointer]) { ptr++; offset++; @@ -1133,14 +1164,11 @@ public class Gnonograms.Region { //carry out same process in reverse to get latest end points rng = n_available_ranges - 1; offset = 0; //start at end of last range NB offset now counts from end - for (int b = bl - 1; b >= 0; b--) { //start at last block length = blocks[available_blocks[b]]; //get length - if (ranges[rng, 1] < (length + offset)) { //doesn't fit rng --; offset = 0; - while (rng >= 0 && ranges[rng, 1] < length) { rng --; //keep skipping if too small } @@ -1151,9 +1179,9 @@ public class Gnonograms.Region { } //look for collision with filled cell - ptr = ranges[rng, 0] + ranges[rng, 1] - (offset + length) - 1; //cell before beginning of block + //cell before beginning of block + ptr = ranges[rng, 0] + ranges[rng, 1] - (offset + length) - 1; start = ptr; - while (ptr >= 0 && !tags[ptr, can_be_empty_pointer]) { ptr --; offset++; @@ -1194,7 +1222,8 @@ public class Gnonograms.Region { remove_block_from_range (available_blocks[b], start, length, 1); } - for (int r = n_available_ranges - 1; r > block_end[b, 0]; r--) { //ranges after possible + //ranges after possible + for (int r = n_available_ranges - 1; r > block_end[b, 0]; r--) { remove_block_from_range (available_blocks[b], ranges[r, 0], ranges[r, 1], 1); } } @@ -1220,16 +1249,12 @@ public class Gnonograms.Region { for (int rng = 0; rng < n_ranges; rng++) { start = ranges[rng, 0]; length = ranges[rng, 1]; - for (idx = start; idx < start + length; idx++) { int count = 0; int impossible = 0; - for (int b = 0; b < n_blocks; b++) { - if (tags[idx, b]) { count++; - if (blocks[b] != length) { tags[idx, b] = false; impossible++; @@ -1238,9 +1263,12 @@ public class Gnonograms.Region { } if (count == impossible) { - record_error ("capped range audit", - "start %i len %i, n_blocks %u count %i, impossible %i filled cell with no owners" - .printf (start, length, n_blocks, count, impossible)); + record_error ( + "capped range audit", + "start %i len %i, n_blocks %u count %i, impossible %i filled cell with no owners".printf ( + start, length, n_blocks, count, impossible + ) + ); return false; } } @@ -1250,14 +1278,15 @@ public class Gnonograms.Region { } private bool available_filled_subregion_audit () { - //test whether there is an unambiguous distribution of available blocks amongs available filled subregions. + //test whether there is an unambiguous distribution of available blocks + //amongst available filled subregions. int idx = 0; int start = 0; int end = n_cells; int region_count = 0; - Range[] available_subregions = new Range[n_cells / 2]; //start and end of each subregion - + //start and end of each subregion + Range[] available_subregions = new Range[n_cells / 2]; while (idx < n_cells) { if (status[idx] != CellState.FILLED) { idx++; @@ -1265,7 +1294,6 @@ public class Gnonograms.Region { } region_count++; - if (region_count <= n_blocks) { start = idx; } else { @@ -1292,7 +1320,6 @@ public class Gnonograms.Region { //now see how many blocks could fit here; int[] available_blocks = get_blocks_available (); int n_available_blocks = available_blocks.length; - if (region_count > n_available_blocks) { return false; } @@ -1307,7 +1334,6 @@ public class Gnonograms.Region { for (int i = 0; i < n_available_blocks; i++) { bl = available_blocks[i]; - if (!tags[first_start, bl]) { available_blocks[i] = -1; block_count --; @@ -1318,7 +1344,6 @@ public class Gnonograms.Region { for (int i = n_available_blocks - 1; i >= 0; i --) { bl = available_blocks[i]; - if (bl >= 0 && !tags[last_end, bl]) { available_blocks[i] = -1; block_count --; @@ -1334,7 +1359,6 @@ public class Gnonograms.Region { int[] candidates = new int[block_count]; int candidates_count = 0; int combined_length = 0; - for (int i = 0; i < n_available_blocks; i++) { if (available_blocks[i] < 0) { continue; @@ -1345,24 +1369,21 @@ public class Gnonograms.Region { } } - combined_length += (candidates_count - 1); //allow for gap of at least 1 between blocks + //allow for gap of at least 1 between blocks + combined_length += (candidates_count - 1); - // for unambiguous assignment all sub regions must be separated by more than + // for unambiguous assignment, all sub regions must be separated by more than // the combined length of the candidate blocks and gaps int overall_length = last_end - first_start + 1; - if (overall_length < combined_length) { return false; } //consecutive regions must be separated so one block cannot cover both //either by finished cell or by distance - for (int ar = 0; ar < region_count - 1; ar++) { - bool regions_are_separate = false; - + var regions_are_separate = false; for (int i = available_subregions[ar].end; i < available_subregions[ar + 1].start; i++) { - if (tags[i, is_finished_pointer]) { regions_are_separate = true; break; @@ -1375,7 +1396,6 @@ public class Gnonograms.Region { start = available_subregions[ar].start; end = available_subregions[ar + 1].end; length = end - start + 1; - if (length <= blocks[candidates[ar]] || length <= blocks[candidates[ar + 1]]) { return false; //too close } @@ -1408,7 +1428,6 @@ public class Gnonograms.Region { // increments/decrements idx until cell of required state // or end of range found. // returns idx of cell with status cs if found else limit - if (limit < -1 || limit > n_cells) { in_error = true; message = "limit < -1 || limit > n_cells"; @@ -1422,7 +1441,6 @@ public class Gnonograms.Region { } for (int i = idx; i != limit; i += direction) { - if (status[i] == cs) { return i; } @@ -1434,17 +1452,13 @@ public class Gnonograms.Region { private int count_consecutive_with_state_from (int cs, int idx, bool forwards) { // count how may consecutive cells of state cs starting at given // index idx (inclusive of starting cell) - int count = 0; - if (forwards && idx >= 0) { - while (idx < n_cells && status[idx] == cs) { count++; idx++; } } else if (!forwards && idx < n_cells) { - while (idx >= 0 && status[idx] == cs) { count++; idx--; @@ -1460,9 +1474,7 @@ public class Gnonograms.Region { private int count_contiguous_with_same_owner_from (int owner, int idx) { // count how may consecutive cells with owner possible starting // at given index idx? - int count = 0; - if (idx >= 0) { while (idx < n_cells && tags[idx, owner] && !tags[idx, is_finished_pointer]) { count++; @@ -1488,7 +1500,6 @@ public class Gnonograms.Region { int start = 0; int length = 0; int idx = 0; - //skip to start of first range; while (idx < n_cells && tags[idx, is_finished_pointer]) { idx++; @@ -1500,9 +1511,7 @@ public class Gnonograms.Region { ranges[range, 0] = start; ranges[range, 2] = 0; ranges[range, 3] = 0; - while (idx < n_cells && !tags[idx, is_finished_pointer]) { - if (!tags[idx, can_be_empty_pointer]) { ranges[range, 2]++; //FILLED } else { @@ -1531,11 +1540,8 @@ public class Gnonograms.Region { private bool match_clue () { //only called when region is completed. Checks whether number of blocks is correct - int count = 0, idx = 0, blk_ptr = 0, blk_counter = 0; - while (idx < n_cells) { - while (idx < n_cells && status[idx] == CellState.EMPTY) { idx++; } @@ -1572,12 +1578,10 @@ public class Gnonograms.Region { private int count_capped_ranges () { // determine location of capped ranges of filled cells (not marked complete) and store in ranges[, ] - int range = 0; int start = 0; int length = 0; int idx = 0; - while (idx < n_cells && status[idx] != CellState.FILLED) { idx++; //skip to beginning of first range } @@ -1588,21 +1592,19 @@ public class Gnonograms.Region { ranges[range, 0] = start; ranges[range, 2] = 0; //not used ranges[range, 3] = 0; //not used - while (idx < n_cells && status[idx] == CellState.FILLED) { idx++; length++; } if ((start == 0 || status[start - 1] == CellState.EMPTY) && - (idx == n_cells || status[idx] == CellState.EMPTY)) { //capped - + (idx == n_cells || status[idx] == CellState.EMPTY) + ) { //capped ranges[range, 1] = length; range++; } idx++; - while (idx < n_cells && status[idx] != CellState.FILLED) { idx++; //skip to beginning of next range } @@ -1613,9 +1615,7 @@ public class Gnonograms.Region { private int count_possible_owners_and_can_be_empty (int cell) { // how many possible owners? Does include can be empty tag! - int count = 0; - if (is_invalid_data (cell)) { in_error = true; message = "count_possible_owners_and_can_be_empty 1"; @@ -1641,9 +1641,7 @@ public class Gnonograms.Region { private int count_cell_state (int cs) { //how many times does state cs occur in range. - int count = 0; - for (int i = 0; i < n_cells; i++) { if (status[i] == cs) { count++; @@ -1655,9 +1653,7 @@ public class Gnonograms.Region { private int[] get_blocks_available () { //array of incomplete block indexes - int[] blocks = {}; - for (int i = 0; i < n_blocks; i++) { if (!completed_blocks[i]) { blocks += i; @@ -1670,19 +1666,15 @@ public class Gnonograms.Region { private int have_same_owner (int cell1, int cell2) { //checks if both the same single possible owner. //return owner if same owner else -1 - int count = 0; int owner = -1; bool tmp; - if (cell1 < 0 || cell1 >= n_cells || cell2 < 0 || cell2 >= n_cells) { in_error = true; message = "have_same_owner"; } else { - for (int i = 0; i < n_blocks; i++) { tmp = tags[cell1, i]; - if (count > 1 || (tmp != tags[cell2, i])) { owner = -1; break; @@ -1699,12 +1691,9 @@ public class Gnonograms.Region { private int get_sole_owner (int cell) { // if only one possible owner (if not empty) then return owner index // else return -1. - int count = 0; int owner = -1; - for (int i = 0; i < n_blocks; i++) { - if (tags[cell, i]) { owner = i; count++; @@ -1721,7 +1710,6 @@ public class Gnonograms.Region { private bool fix_block_in_range (int block, int start, int length) { // block must be limited to range var changed = false; - if (is_invalid_data (start, block, length)) { in_error = true; message = "fix_block_in_range"; @@ -1748,11 +1736,8 @@ public class Gnonograms.Region { private int find_largest_possible_block_for_cell (int cell) { // find the largest incomplete block possible for given cell - int maxsize = -1; - for (int i = 0; i < n_blocks; i++) { - if (!tags[cell, i]) { continue; // not possible } @@ -1769,9 +1754,7 @@ public class Gnonograms.Region { private int find_smallest_possible_block_for_cell (int cell) { // find the smallest incomplete block possible for given cell - int minsize = 9999; - for (int i = 0; i < n_blocks; i++) { if (!tags[cell, i]) { continue; // not possible @@ -1797,10 +1780,8 @@ public class Gnonograms.Region { //bi-directional forward = 1 backward = -1 //if reverse direction then equivalent forward range is used //only changes tags - int length = direction > 0 ? n_cells - start : start + 1; start = direction > 0 ? start : 0; - if (length > 0) { remove_block_from_range (block, start, length, 1); } @@ -1811,7 +1792,6 @@ public class Gnonograms.Region { //bi-directional forward = 1 backward = -1 //if reverse direction then equivalent forward range is used //only changes tags - if (direction < 0) { start = start - length + 1; } @@ -1820,7 +1800,6 @@ public class Gnonograms.Region { in_error = true; message = "remove_block_from_range"; } else { - for (int i = start; i < start + length; i++) { tags[i, block] = false; } @@ -1831,7 +1810,6 @@ public class Gnonograms.Region { //returns true - always changes a cell status if not in error bool changed = false; int length = blocks[block]; - if (direction < 0) { start = start - length + 1; } @@ -1850,7 +1828,6 @@ public class Gnonograms.Region { completed_blocks[block] = true; set_range_owner (block, start, length, true, false); - if (start > 0 && !tags[start - 1, is_finished_pointer]) { changed = true; set_cell_empty (start - 1); @@ -1868,7 +1845,6 @@ public class Gnonograms.Region { //taking into account minimum distance between blocks. // constrain the preceding blocks if this are at least two int l; - if (block > 1) { //at least third block l = 0; @@ -1881,7 +1857,6 @@ public class Gnonograms.Region { // constrain the following blocks if there are at least two if (block < n_blocks - 2) { l = 0; - for (int bl = block + 2; bl <= n_blocks - 1; bl++) { l = l + blocks[bl - 1] + 1; // length of exclusion zone for this block remove_block_from_range (bl, start + length + 1, l, 1); @@ -1893,14 +1868,12 @@ public class Gnonograms.Region { private bool set_range_owner (int owner, int start, int length, bool exclusive, bool can_be_empty) { bool changed = false; - if (is_invalid_data (start, owner, length)) { in_error = true; message = "set_range_owner 1"; return false; } else { int blocklength = blocks[owner]; - for (int cell = start; cell < start + length; cell++) { set_cell_owner (cell, owner, exclusive, can_be_empty); } @@ -1914,25 +1887,21 @@ public class Gnonograms.Region { } int bstart = int.min (start - 1, start + length - blocklength); - if (bstart >= 0) { remove_block_from_cell_to_end (owner, bstart - 1, -1); } int bend = int.max (start + length, start + blocklength); - if (bend < n_cells) { remove_block_from_cell_to_end (owner, bend, 1); } int earliestend = start + length; - for (int bl = n_blocks - 1; bl > owner; bl --) { //following blocks cannot be earlier remove_block_from_cell_to_end (bl, earliestend, -1); } int lateststart = start - 1; - for (int bl = 0; bl < owner; bl++) { //preceding blocks cannot be later remove_block_from_cell_to_end (bl, lateststart, 1); } @@ -1946,15 +1915,19 @@ public class Gnonograms.Region { //exclusive - cant be any other block here //can be empty - self evident bool changed = false; - if (is_invalid_data (cell, owner)) { - record_error ("set_cell_owner", - "invalid data %i, %i, %s, %s" - .printf (cell, owner, exclusive.to_string (), can_be_empty.to_string ())); - + record_error ( + "set_cell_owner", + "invalid data %i, %i, %s, %s".printf ( + cell, owner, exclusive.to_string (), can_be_empty.to_string () + ) + ); } else if (status[cell] == CellState.EMPTY) {// do nothing - not necessarily an error } else if (status[cell] == CellState.COMPLETED && tags[cell, owner] == false) { - record_error ("set_cell_owner", "contradiction cell " + cell.to_string () + " filled but cannot be owner"); + record_error ( + "set_cell_owner", + "contradiction cell " + cell.to_string () + " filled but cannot be owner" + ); } else { if (exclusive) { for (int i = 0; i < n_blocks; i++) { @@ -1982,7 +1955,6 @@ public class Gnonograms.Region { } else if (is_cell_filled (cell)) { record_error ("set_cell_empty", "cell " + cell.to_string () + " is filled"); } else { - for (int i = 0; i < n_blocks; i++) { tags[cell, i] = false; } @@ -2019,28 +1991,30 @@ public class Gnonograms.Region { private bool totals_changed () { //has number of filled or unknown cells changed? - bool changed = false; int _unknown = count_cell_state (CellState.UNKNOWN); int _filled = count_cell_state (CellState.FILLED); int _completed = count_cell_state (CellState.COMPLETED); - if (_unknown != this.unknown) { changed = true; this.unknown = _unknown; this.filled = _filled; - if (_filled + _completed > block_total) { - record_error ("totals changed", - ("too many filled cells filled %i, completed %i, block total %i") - .printf (_filled, _completed, block_total)); + record_error ( + "totals changed", + "too many filled cells filled %i, completed %i, block total %i".printf ( + _filled, _completed, block_total + ) + ); } else if (this.unknown == 0) { if (_filled + _completed < block_total) { - record_error ("totals changed", - ("too few filled cells filled %i, completed %i, block total %i") - .printf (_filled, _completed, block_total)); - + record_error ( + "totals changed", + "too few filled cells filled %i, completed %i, block total %i".printf ( + _filled, _completed, block_total + ) + ); } else if (match_clue ()) { this.is_completed = true; } else { @@ -2056,26 +2030,27 @@ public class Gnonograms.Region { private void get_status () { //transfers cell statuses from grid to internal range status array - grid.get_array (index, is_column, ref temp_status); - for (int i = 0; i < n_cells; i++) { - switch (temp_status[i]) { - case CellState.EMPTY : if (!tags[i, can_be_empty_pointer]) { - record_error ("get_status", "cell " + i.to_string () + " cannot be empty"); + record_error ( + "get_status", + "cell " + i.to_string () + " cannot be empty" + ); } else { status[i] = CellState.EMPTY; } break; - case CellState.FILLED : //dont overwrite COMPLETE status if (status[i] == CellState.EMPTY) { - record_error ("get_status", "cell " + i.to_string () + " cannot be filled"); + record_error ( + "get_status", + "cell " + i.to_string () + " cannot be filled" + ); } if (status[i] == CellState.UNKNOWN) { @@ -2083,7 +2058,6 @@ public class Gnonograms.Region { } break; - default: break; } @@ -2095,7 +2069,8 @@ public class Gnonograms.Region { private void put_status () { //use temp_status2 to ovoid overwriting original input - needed for debugging for (int i = 0; i < n_cells; i++) { - temp_status2[i] = (status[i] == CellState.COMPLETED ? CellState.FILLED : status[i]); + temp_status2[i] = status[i] == CellState.COMPLETED ? + CellState.FILLED : status[i]; } grid.set_array (index, is_column, temp_status2); @@ -2139,7 +2114,8 @@ public class Gnonograms.Region { } if (!tags[i, can_be_empty_pointer]) { //cannot be EMPTY - status[i] = (tags[i, is_finished_pointer] ? CellState.COMPLETED : CellState.FILLED); + status[i] = tags[i, is_finished_pointer] ? + CellState.COMPLETED : CellState.FILLED; continue; } @@ -2153,7 +2129,8 @@ public class Gnonograms.Region { private void record_error (string method, string errmessage) { in_error = true; - message = "%s Region %u Record error in %s : %s \n" - .printf (is_column ? "COL" : "ROW", index, method, errmessage); + message = "%s Region %u Record error in %s : %s \n".printf ( + is_column ? "COL" : "ROW", index, method, errmessage + ); } } diff --git a/libcore/Solver.vala b/libcore/Solver.vala index 164724e..5bf6c71 100644 --- a/libcore/Solver.vala +++ b/libcore/Solver.vala @@ -226,9 +226,13 @@ } #if WITH_DEBUGGING - public Gee.ArrayQueue debug (uint idx, bool is_column, string[] row_clues, - string[] col_clues, My2DCellArray working) { - + public Gee.ArrayQueue debug ( + uint idx, + bool is_column, + string[] row_clues, + string[] col_clues, + My2DCellArray working + ) { initialize (row_clues, col_clues, working, null); var moves = new Gee.ArrayQueue (); @@ -259,7 +263,11 @@ } #endif - public Gee.ArrayQueue hint (string[] row_clues, string[] col_clues, My2DCellArray working) { + public Gee.ArrayQueue hint ( + string[] row_clues, + string[] col_clues, + My2DCellArray working + ) { initialize (row_clues, col_clues, working, null); bool changed = false; uint count = 0; @@ -282,8 +290,8 @@ } while (!changed && count < 2 && - state != SolverState.ERROR) { /* May require two passes before a state changes */ - + state != SolverState.ERROR + ) { /* May require two passes before a state changes */ changed = false; count++; foreach (Region r in regions) { @@ -385,7 +393,6 @@ while (state == SolverState.UNDEFINED) { changed_count++; - if (!guesser.next_guess ()) { state = SolverState.NO_SOLUTION; if (best_guess.equal (NULL_CELL)) { // No improvement from last round @@ -404,7 +411,6 @@ result = yield simple_solver (); initial_state = state; - if (initial_state == SolverState.NO_SOLUTION) { empty = solution.count_state (CellState.EMPTY); if (empty < min_empty_cells) { @@ -421,7 +427,6 @@ } contra = result; - /* Try opposite to check whether ambiguous or unique */ guesser.invert_previous_guess (); result = yield simple_solver (); @@ -475,7 +480,7 @@ result = yield simple_solver (); state = SolverState.AMBIGUOUS; } - } else if (initial_state == SolverState.ERROR) { // already checked for too may passes to contradiction. + } else if (initial_state == SolverState.ERROR) { guesser.initialize (); /* Continue from this position */ state = SolverState.UNDEFINED; @@ -534,7 +539,6 @@ /** Only call if simple solver used **/ private Difficulty passes_to_grade (uint passes) { Difficulty result; - if (passes == 0) { result = Difficulty.UNDEFINED; } else if (state == SolverState.ADVANCED) { @@ -665,7 +669,7 @@ c++; cdir = 0; rdir = -1; - r--; + r--; } else if (rdir == -1 && r <= turn) { //back across bottom lh edge reached r++; turn++; diff --git a/libcore/utils.vala b/libcore/utils.vala index 26c55df..9279a21 100644 --- a/libcore/utils.vala +++ b/libcore/utils.vala @@ -240,11 +240,12 @@ namespace Gnonograms.Utils { return sb.str; } - private static int show_dlg (string primary_text, - Gtk.MessageType type, - string? secondary_text, - Gtk.Window? parent) { - + private static int show_dlg ( + string primary_text, + Gtk.MessageType type, + string? secondary_text, + Gtk.Window? parent + ) { string icon_name = ""; var buttons = Gtk.ButtonsType.CLOSE; switch (type) { @@ -269,9 +270,11 @@ namespace Gnonograms.Utils { assert_not_reached (); } - var dialog = new Granite.MessageDialog.with_image_from_icon_name (primary_text, - secondary_text ?? "", - icon_name, buttons); + var dialog = new Granite.MessageDialog.with_image_from_icon_name ( + primary_text, + secondary_text ?? "", + icon_name, buttons + ); dialog.set_transient_for (parent); if (type == Gtk.MessageType.QUESTION) { @@ -290,17 +293,19 @@ namespace Gnonograms.Utils { return response; } - public static void show_error_dialog (string primary_text, - string? secondary_text = null, - Gtk.Window? parent = null) { - + public static void show_error_dialog ( + string primary_text, + string? secondary_text = null, + Gtk.Window? parent = null + ) { show_dlg (primary_text, Gtk.MessageType.ERROR, secondary_text, parent); } - public static bool show_confirm_dialog (string primary_text, - string? secondary_text = null, - Gtk.Window? parent = null) { - + public static bool show_confirm_dialog ( + string primary_text, + string? secondary_text = null, + Gtk.Window? parent = null + ) { var response = show_dlg ( primary_text, Gtk.MessageType.QUESTION, @@ -310,13 +315,15 @@ namespace Gnonograms.Utils { return response == Gtk.ResponseType.YES; } - public static string? get_open_save_path (Gtk.Window? parent, - string dialogname, - bool save, - string start_folder_path, - string basename) { + public static string? get_open_save_path ( + Gtk.Window? parent, + string dialogname, + bool save, + string start_folder_path, + string basename + ) { string? file_path = null; - string button_label = save ? _("Save") : _("Open"); + var button_label = save ? _("Save") : _("Open"); var gtk_action = save ? Gtk.FileChooserAction.SAVE : Gtk.FileChooserAction.OPEN; var dialog = new Gtk.FileChooserNative ( dialogname, @@ -358,9 +365,9 @@ namespace Gnonograms.Utils { return file_path; } - public Gdk.Rectangle get_monitor_area (Gdk.Surface surface) { - var display = Gdk.Display.get_default (); - var monitor = display.get_monitor_at_surface (surface); - return monitor.get_geometry (); - } + // public Gdk.Rectangle get_monitor_area (Gdk.Surface surface) { + // var display = Gdk.Display.get_default (); + // var monitor = display.get_monitor_at_surface (surface); + // return monitor.get_geometry (); + // } } diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index ead9de9..4edadd8 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -20,7 +20,8 @@ public class Gnonograms.ClueBox : Gtk.Box { public unowned View view { get; construct; } public int font_size { get; private set; } - public uint n_cells { get; set; default = 0; } // The number of cells each clue addresses, monitored by clues + // The number of cells each clue addresses, monitored by clues + public uint n_cells { get; set; default = 0; } private Gee.ArrayList clues; public ClueBox (Gtk.Orientation _orientation, View view) { @@ -48,6 +49,7 @@ public class Gnonograms.ClueBox : Gtk.Box { foreach (var clue in clues) { remove (clue.label); } + clues.clear (); n_cells = new_n_cells; for (int index = 0; index < new_n_clues; index++) { @@ -55,8 +57,6 @@ public class Gnonograms.ClueBox : Gtk.Box { clues.add (clue); append (clue.label); } - - // set_size (); }); view.notify["cell-size"].connect (set_size); diff --git a/src/HeaderBar/HeaderButton.vala b/src/HeaderBar/HeaderButton.vala index a46faa3..714cf2e 100644 --- a/src/HeaderBar/HeaderButton.vala +++ b/src/HeaderBar/HeaderButton.vala @@ -34,9 +34,4 @@ child = image; add_css_class ("flat"); } - - construct { - - } - } diff --git a/src/HeaderBar/ProgressIndicator.vala b/src/HeaderBar/ProgressIndicator.vala index cf42978..f0148af 100644 --- a/src/HeaderBar/ProgressIndicator.vala +++ b/src/HeaderBar/ProgressIndicator.vala @@ -52,6 +52,7 @@ }; cancel_button.child = img; cancel_button.add_css_class ("warn"); + cancel_button.add_css_class ("flat"); img.add_css_class ("warn"); append (cancel_button); diff --git a/src/HeaderBar/RestartButton.vala b/src/HeaderBar/RestartButton.vala index 81a7c3c..7ad25c7 100644 --- a/src/HeaderBar/RestartButton.vala +++ b/src/HeaderBar/RestartButton.vala @@ -22,7 +22,6 @@ construct { restart_destructive = false; - notify["restart-destructive"].connect (() => { if (restart_destructive) { add_css_class ("warn"); diff --git a/src/View.vala b/src/View.vala index 037b19e..9ebc4da 100644 --- a/src/View.vala +++ b/src/View.vala @@ -29,26 +29,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private const string PAINT_EMPTY_ACCEL = "e"; // Must be lower case private const string PAINT_UNKNOWN_ACCEL = "x"; // Must be lower case -#if WITH_DEBUGGING - public signal void debug_request (uint idx, bool is_column); -#endif - - public Model model { get; construct; } - public Controller controller { get; construct; } - public Cell current_cell { get; set; } - public Cell previous_cell { get; set; } - public Difficulty generator_grade { get; set; } - public Difficulty game_grade { get; set; default = Difficulty.UNDEFINED;} - public int cell_size { get; set; default = 32; } - public string game_name { get { return controller.game_name; } } - public bool strikeout_complete { get; set; } - public bool readonly { get; set; default = false;} - public bool can_go_back { get; set; } - public bool can_go_forward { get; set; } - public bool restart_destructive { get; set; default = false;} - - public SimpleActionGroup view_actions { get; construct; } - public static Gee.MultiMap action_accelerators = new Gee.HashMultiMap (); public const string ACTION_GROUP = "win"; public const string ACTION_PREFIX = ACTION_GROUP + "."; public const string ACTION_UNDO = "action-undo"; @@ -77,6 +57,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public const string ACTION_DEBUG_ROW = "action-debug-row"; public const string ACTION_DEBUG_COL = "action-debug-col"; #endif + public static Gee.MultiMap action_accelerators; + public static Gtk.Application app; private static GLib.ActionEntry [] view_action_entries = { {ACTION_UNDO, action_undo}, {ACTION_REDO, action_redo}, @@ -102,7 +84,25 @@ public class Gnonograms.View : Gtk.ApplicationWindow { {ACTION_OPTIONS, action_options} }; - public static Gtk.Application app; +#if WITH_DEBUGGING + public signal void debug_request (uint idx, bool is_column); +#endif + + public Model model { get; construct; } + public Controller controller { get; construct; } + public Cell current_cell { get; set; } + public Cell previous_cell { get; set; } + public Difficulty generator_grade { get; set; } + public Difficulty game_grade { get; set; default = Difficulty.UNDEFINED;} + public int cell_size { get; set; default = 32; } + public string game_name { get { return controller.game_name; } } + public bool strikeout_complete { get; set; } + public bool readonly { get; set; default = false;} + public bool can_go_back { get; set; } + public bool can_go_forward { get; set; } + public bool restart_destructive { get; set; default = false;} + public SimpleActionGroup view_actions { get; construct; } + private ClueBox row_clue_box; private ClueBox column_clue_box; private CellGrid cell_grid; @@ -137,6 +137,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } static construct { + action_accelerators = new Gee.HashMultiMap (); app = (Gtk.Application)(Application.get_default ()); #if WITH_DEBUGGING warning ("WITH DEBUGGING"); @@ -430,9 +431,21 @@ public class Gnonograms.View : Gtk.ApplicationWindow { child = main_grid; var flags = BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE; - bind_property ("restart-destructive", restart_button, "restart-destructive", BindingFlags.SYNC_CREATE); - bind_property ("current-cell", cell_grid, "current-cell", BindingFlags.BIDIRECTIONAL); - bind_property ("previous-cell", cell_grid, "previous-cell", BindingFlags.BIDIRECTIONAL); + bind_property ( + "restart-destructive", + restart_button, "restart-destructive", + BindingFlags.SYNC_CREATE + ); + bind_property ( + "current-cell", + cell_grid, "current-cell", + BindingFlags.BIDIRECTIONAL + ); + bind_property ( + "previous-cell", + cell_grid, "previous-cell", + BindingFlags.BIDIRECTIONAL + ); mode_switch.notify["active"].connect (() => { controller.game_state = mode_switch.active ? GameState.SOLVING : GameState.SETTING; @@ -554,11 +567,17 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public void update_clues_from_solution () { for (int r = 0; r < controller.dimensions.height; r++) { - row_clue_box.update_clue_text (r, model.get_label_text_from_solution (r, false)); + row_clue_box.update_clue_text ( + r, + model.get_label_text_from_solution (r, false) + ); } for (int c = 0; c < controller.dimensions.width; c++) { - column_clue_box.update_clue_text (c, model.get_label_text_from_solution (c, true)); + column_clue_box.update_clue_text ( + c, + model.get_label_text_from_solution (c, true) + ); } update_all_labels_completeness (); From f165b5bb0467dea212fda2f33072c5694e783a06 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 20 Feb 2024 17:13:43 +0000 Subject: [PATCH 050/142] Remove unused property --- libcore/widgets/Clue.vala | 1 - 1 file changed, 1 deletion(-) diff --git a/libcore/widgets/Clue.vala b/libcore/widgets/Clue.vala index fc0a6ec..6f5d660 100644 --- a/libcore/widgets/Clue.vala +++ b/libcore/widgets/Clue.vala @@ -20,7 +20,6 @@ class Gnonograms.Clue : Object { public Gtk.Label label { get; construct; } public unowned ClueBox cluebox { get; construct; } - private int _fontsize; private string _text; /* text of clue in horizontal form */ public string text { From a30883955190cb9d66ad35df540691660deba455 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 21 Feb 2024 10:30:03 +0000 Subject: [PATCH 051/142] Use Gtk.DropDown in AppPopover --- libcore/Enums.vala | 11 +++++++++-- src/HeaderBar/AppPopover.vala | 16 +++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/libcore/Enums.vala b/libcore/Enums.vala index 061b2f2..de72a2a 100644 --- a/libcore/Enums.vala +++ b/libcore/Enums.vala @@ -58,8 +58,15 @@ namespace Gnonograms { } } - public static Difficulty[] all_human () { - return { EASY, MODERATE, HARD, CHALLENGING, ADVANCED, MAXIMUM }; + public static string[] all_human () { + return { + EASY.to_string (), + MODERATE.to_string (), + HARD.to_string (), + CHALLENGING.to_string (), + ADVANCED.to_string (), + MAXIMUM.to_string () + }; } } diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 5b14637..13eb93f 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -19,7 +19,7 @@ public class Gnonograms.AppPopover : Gtk.Popover { public signal void apply_settings (); - private Gtk.ComboBoxText grade_setting; + private Gtk.DropDown grade_setting; private Gtk.SpinButton row_setting; private Gtk.SpinButton column_setting; private Gtk.Entry title_setting; @@ -28,11 +28,16 @@ public class Gnonograms.AppPopover : Gtk.Popover { public Difficulty grade { get { - return (Difficulty)(int.parse (grade_setting.get_active_id ())); + return (Difficulty) (grade_setting.get_selected ()); } set { - grade_setting.active_id = ((uint)value).clamp (MIN_GRADE, Difficulty.MAXIMUM).to_string (); + grade_setting.set_selected ( + ((uint) value).clamp ( + MIN_GRADE, + Difficulty.MAXIMUM + ) + ); } } @@ -94,10 +99,7 @@ public class Gnonograms.AppPopover : Gtk.Popover { construct { autohide = false; - grade_setting = new Gtk.ComboBoxText (); - foreach (Difficulty d in Difficulty.all_human ()) { - grade_setting.append (((uint)d).to_string (), d.to_string ()); - } + grade_setting = new Gtk.DropDown.from_strings (Difficulty.all_human ()); row_setting = new Gtk.SpinButton ( new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), From d0e08b2c3e6a7a41054b32cf83ce5d500c1e6db4 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 21 Feb 2024 11:39:36 +0000 Subject: [PATCH 052/142] Use Gtk.FileDialog --- libcore/Filereader.vala | 25 +++++++-------- libcore/Filewriter.vala | 23 ++++++++------ libcore/utils.vala | 50 ++++++++++-------------------- src/Controller.vala | 68 ++++++++++++++++++++++------------------- src/View.vala | 4 +-- 5 files changed, 82 insertions(+), 88 deletions(-) diff --git a/libcore/Filereader.vala b/libcore/Filereader.vala index baadb04..31a9f64 100644 --- a/libcore/Filereader.vala +++ b/libcore/Filereader.vala @@ -53,11 +53,15 @@ public class Gnonograms.Filereader : Object { } } - public Filereader (Gtk.Window? parent, string? load_dir_path, File? game) throws GLib.IOError { - Object (game_file: game); - + public async void read ( + Gtk.Window? parent, + string? load_dir_path, + File? game + ) throws Error { if (game == null) { - game_file = get_load_game_file (parent, load_dir_path); + game_file = yield get_load_game_file (parent, load_dir_path); + } else { + game_file = game; } if (game_file == null) { @@ -73,11 +77,13 @@ public class Gnonograms.Filereader : Object { } parse_gnonogram_game_file (stream); - } - private File? get_load_game_file (Gtk.Window? parent, string? load_dir_path) { - string? path = Utils.get_open_save_path ( + private async File? get_load_game_file ( + Gtk.Window? parent, + string? load_dir_path + ) throws Error { + return yield Utils.get_open_save_file ( parent, _("Choose a puzzle"), false, @@ -85,11 +91,6 @@ public class Gnonograms.Filereader : Object { "" ); - if (path == null || path == "") { - return null; - } else { - return File.new_for_path (path); - } } private void parse_gnonogram_game_file (DataInputStream stream) throws GLib.IOError { diff --git a/libcore/Filewriter.vala b/libcore/Filewriter.vala index 167ca74..a610217 100644 --- a/libcore/Filewriter.vala +++ b/libcore/Filewriter.vala @@ -45,7 +45,7 @@ public class Gnonograms.Filewriter : Object { string[] row_clues, string[] col_clues, History? history, - bool save_solution) throws IOError { + bool save_solution) { Object ( name: _(UNTITLED_NAME), @@ -64,10 +64,11 @@ public class Gnonograms.Filewriter : Object { } /*** Writes minimum information required for valid game file ***/ - public void write_game_file (string? save_dir_path = null, - string? path = null, - string? _name = null) throws IOError { - + public async void write_game_file ( + string? save_dir_path = null, + string? path = null, + string? _name = null + ) throws Error { if (_name != null) { name = _name; } else { @@ -75,12 +76,16 @@ public class Gnonograms.Filewriter : Object { } if (path == null || path.length <= 4) { - game_path = Utils.get_open_save_path (parent, + var game_file = yield Utils.get_open_save_file (parent, _("Name and save this puzzle"), true, save_dir_path, name ); + + if (game_file != null) { + game_path = game_file.get_path (); + } } else { game_path = path; } @@ -164,16 +169,16 @@ public class Gnonograms.Filewriter : Object { } /*** Writes complete information to reload game state ***/ - public void write_position_file (string? save_dir_path = null, + public async void write_position_file (string? save_dir_path = null, string? path = null, - string? name = null) throws IOError { + string? name = null) throws Error { if (working == null) { throw (new IOError.NOT_INITIALIZED ("No working grid to save")); } else if (game_state == GameState.UNDEFINED) { throw (new IOError.NOT_INITIALIZED ("No game state to save")); } - write_game_file (save_dir_path, path, name ); + yield write_game_file (save_dir_path, path, name ); stream.printf ("[Working grid]\n"); stream.printf (working.to_string ()); stream.printf ("[State]\n"); diff --git a/libcore/utils.vala b/libcore/utils.vala index 9279a21..b7696fd 100644 --- a/libcore/utils.vala +++ b/libcore/utils.vala @@ -315,54 +315,36 @@ namespace Gnonograms.Utils { return response == Gtk.ResponseType.YES; } - public static string? get_open_save_path ( + public static async File? get_open_save_file ( Gtk.Window? parent, string dialogname, bool save, string start_folder_path, string basename - ) { - string? file_path = null; + ) throws Error { var button_label = save ? _("Save") : _("Open"); - var gtk_action = save ? Gtk.FileChooserAction.SAVE : Gtk.FileChooserAction.OPEN; - var dialog = new Gtk.FileChooserNative ( - dialogname, - parent, - gtk_action, - button_label, - _("Cancel") - ); + var dialog = new Gtk.FileDialog () { + title = dialogname, + accept_label = button_label, + initial_folder = File.new_for_path (start_folder_path), + initial_name = basename, + modal = true + + }; dialog.set_modal (true); - try { - if (save) { - dialog.set_current_folder (File.new_for_path (start_folder_path)); - if (basename != null) { - dialog.set_current_name (basename); - } - } else { - try { - dialog.set_current_folder (File.new_for_path (start_folder_path)); - } catch (Error e) { - warning ("Error setting current folder: %s", e.message); - } - } - } catch (Error e) { - warning ("Error configuring FileChooser dialog: %s", e.message); + File? result = null; + if (save) { + result = yield (dialog.save (parent, null)); + } else { + result = yield (dialog.save (parent, null)); } - dialog.response.connect ((response) => { - if (response == Gtk.ResponseType.ACCEPT) { - file_path = dialog.get_file ().get_path (); - } - dialog.destroy (); - }); - dialog.show (); - return file_path; + return result; } // public Gdk.Rectangle get_monitor_area (Gdk.Surface surface) { diff --git a/src/Controller.vala b/src/Controller.vala index ba5c930..5c9b7f1 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -216,13 +216,13 @@ public class Gnonograms.Controller : GLib.Object { try { var current_game_file = File.new_for_path (temporary_game_path); current_game_file.@delete (); - } catch (GLib.Error e) { + } catch (Error e) { /* Error normally thrown on first run */ debug ("Error deleting temporary game file %s - %s", temporary_game_path, e.message); } finally { debug ("writing unsaved game to %s", temporary_game_path); /* Save solution and current state */ - write_game (temporary_game_path, true); + write_game.begin (temporary_game_path, true); } } else { warning ("No temporary game path"); @@ -258,39 +258,44 @@ public class Gnonograms.Controller : GLib.Object { } } - private string? write_game (string? path, bool save_state = false) { + private async string? write_game (string? path, bool save_state = false) { Filewriter? file_writer = null; var gs = game_state; - game_state = GameState.UNDEFINED; - try { - file_writer = new Filewriter (window, - dimensions, - view.get_clues (false), - view.get_clues (true), - history, - !model.solution_is_blank () - ); - - file_writer.difficulty = view.game_grade; - file_writer.game_state = gs; - file_writer.working = model.copy_working_data (); - if (file_writer.save_solution) { - file_writer.solution = model.copy_solution_data (); - } + file_writer = new Filewriter ( + window, + dimensions, + view.get_clues (false), + view.get_clues (true), + history, + !model.solution_is_blank () + ); - file_writer.is_readonly = is_readonly; + file_writer.difficulty = view.game_grade; + file_writer.game_state = gs; + file_writer.working = model.copy_working_data (); + if (file_writer.save_solution) { + file_writer.solution = model.copy_solution_data (); + } + file_writer.is_readonly = is_readonly; + try { if (save_state) { - file_writer.write_position_file (saved_games_folder, path, game_name); + yield file_writer.write_position_file (saved_games_folder, path, game_name); } else { - file_writer.write_game_file (saved_games_folder, path, game_name); + yield file_writer.write_game_file ( + saved_games_folder, + path, + game_name + ); } - - } catch (IOError e) { + } catch (Error e) { if (!(e is IOError.CANCELLED)) { var basename = Path.get_basename (file_writer.game_path); - Utils.show_error_dialog (_("Unable to save %s").printf (basename), e.message); + Utils.show_error_dialog ( + _("Unable to save %s").printf (basename), + e.message + ); } return null; @@ -318,8 +323,9 @@ public class Gnonograms.Controller : GLib.Object { game_state = GameState.UNDEFINED; clear_history (); + reader = new Filereader (); try { - reader = new Filereader ( + yield reader.read ( window, Environment.get_user_special_dir (UserDirectory.DOCUMENTS), game @@ -509,11 +515,11 @@ public class Gnonograms.Controller : GLib.Object { return Gdk.EVENT_PROPAGATE; } - public void save_game () { + public async void save_game () { if (is_readonly || current_game_path == "") { - save_game_as (); + yield save_game_as (); } else { - var path = write_game (current_game_path, false); + var path = yield write_game (current_game_path, false); if (path != null && path != "") { current_game_path = path; notify_saved (path); @@ -521,9 +527,9 @@ public class Gnonograms.Controller : GLib.Object { } } - public void save_game_as () { + public async void save_game_as () { /* Filewriter will request save location, no solution saved as default */ - var path = write_game (null, false); + var path = yield write_game (null, false); if (path != null) { current_game_path = path; notify_saved (path); diff --git a/src/View.vala b/src/View.vala index 9ebc4da..3fdbb59 100644 --- a/src/View.vala +++ b/src/View.vala @@ -804,11 +804,11 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } private void action_save () { - controller.save_game (); + controller.save_game.begin (); } private void action_save_as () { - controller.save_game_as (); + controller.save_game_as.begin (); } private void action_zoom_in () { From e9b30f889fa15bf67f78e172391a782acae3171b Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 21 Feb 2024 11:50:11 +0000 Subject: [PATCH 053/142] Modernise license and copyright --- LICENSE | 674 ----------------------- libcore/Constants.vala | 22 +- libcore/Enums.vala | 21 +- libcore/Filereader.vala | 22 +- libcore/Filewriter.vala | 22 +- libcore/History.vala | 21 +- libcore/Model.vala | 20 +- libcore/Move.vala | 21 +- libcore/My2DCellArray.vala | 21 +- libcore/Region.vala | 21 +- libcore/Solver.vala | 21 +- libcore/Structs.vala | 21 +- libcore/utils.vala | 20 +- libcore/widgets/Cellgrid.vala | 21 +- libcore/widgets/Clue.vala | 21 +- libcore/widgets/Cluebox.vala | 21 +- src/Application.vala | 20 +- src/Controller.vala | 20 +- src/HeaderBar/AppPopover.vala | 24 +- src/HeaderBar/HeaderButton.vala | 21 +- src/HeaderBar/ProgressIndicator.vala | 20 +- src/HeaderBar/RestartButton.vala | 19 +- src/View.vala | 20 +- src/services/RandomGameGenerator.vala | 20 +- src/services/RandomPatternGenerator.vala | 20 +- 25 files changed, 97 insertions(+), 1077 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 9cecc1d..0000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - {project} Copyright (C) {year} {fullname} - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/libcore/Constants.vala b/libcore/Constants.vala index 620ab42..e437749 100644 --- a/libcore/Constants.vala +++ b/libcore/Constants.vala @@ -1,23 +1,9 @@ - -/* Constants.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ - namespace Gnonograms { public const Cell NULL_CELL = { uint.MAX, uint.MAX, CellState.UNDEFINED }; public const uint MAXSIZE = 54; // max number rows or columns diff --git a/libcore/Enums.vala b/libcore/Enums.vala index de72a2a..c970790 100644 --- a/libcore/Enums.vala +++ b/libcore/Enums.vala @@ -1,22 +1,9 @@ -/* Enums.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ - namespace Gnonograms { public enum Difficulty { TRIVIAL = 0, diff --git a/libcore/Filereader.vala b/libcore/Filereader.vala index 31a9f64..cbe66d1 100644 --- a/libcore/Filereader.vala +++ b/libcore/Filereader.vala @@ -1,23 +1,9 @@ -/* Filereader.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Author: Jeremy Wootten < jeremwootten@gmail.com > + * Authored by: Jeremy Wootten */ - public class Gnonograms.Filereader : Object { public string err_msg = ""; diff --git a/libcore/Filewriter.vala b/libcore/Filewriter.vala index a610217..4f53018 100644 --- a/libcore/Filewriter.vala +++ b/libcore/Filewriter.vala @@ -1,23 +1,9 @@ -/* Filewriter.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Author: Jeremy Wootten < jeremwootten@gmail.com > + * Authored by: Jeremy Wootten */ - public class Gnonograms.Filewriter : Object { public DateTime date { get; construct; } public History? history { get; construct; } diff --git a/libcore/History.vala b/libcore/History.vala index a243a9c..2e84ef0 100644 --- a/libcore/History.vala +++ b/libcore/History.vala @@ -1,22 +1,9 @@ -/* History.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ - public class Gnonograms.History : GLib.Object { public bool can_go_back { get; private set; } public bool can_go_forward { get; private set; } diff --git a/libcore/Model.vala b/libcore/Model.vala index 654cbe8..72bf138 100644 --- a/libcore/Model.vala +++ b/libcore/Model.vala @@ -1,20 +1,8 @@ -/* Model.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ public class Gnonograms.Model : GLib.Object { public signal void changed (); diff --git a/libcore/Move.vala b/libcore/Move.vala index ff35e8b..a7dc987 100644 --- a/libcore/Move.vala +++ b/libcore/Move.vala @@ -1,22 +1,9 @@ -/* Move.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ - public class Gnonograms.Move { public static Move null_move = new Move (NULL_CELL, CellState.UNDEFINED); diff --git a/libcore/My2DCellArray.vala b/libcore/My2DCellArray.vala index a7ef396..f087074 100644 --- a/libcore/My2DCellArray.vala +++ b/libcore/My2DCellArray.vala @@ -1,22 +1,9 @@ -/* My2DCellArray.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ - public class Gnonograms.My2DCellArray : Object { public uint rows { get; construct; } public uint cols { get; construct; } diff --git a/libcore/Region.vala b/libcore/Region.vala index 8d8abd3..e9b962c 100644 --- a/libcore/Region.vala +++ b/libcore/Region.vala @@ -1,22 +1,9 @@ -/* Region.vala - * Copyright (C) 2010 -2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see < http://www.gnu.org/licenses/>. - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ - /* A region consists of a one dimensional array of cells, corresponding to a row or column of the puzzle. Associated with this are: diff --git a/libcore/Solver.vala b/libcore/Solver.vala index 5bf6c71..6c808ce 100644 --- a/libcore/Solver.vala +++ b/libcore/Solver.vala @@ -1,22 +1,9 @@ -/* Solver.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ - public class Gnonograms.Solver : Object { public SolverState state { get; set; } public My2DCellArray grid { get; protected set; } // Shared with Regions which can update the contents diff --git a/libcore/Structs.vala b/libcore/Structs.vala index 0e7a74d..3a5abbc 100644 --- a/libcore/Structs.vala +++ b/libcore/Structs.vala @@ -1,22 +1,9 @@ -/* Structs.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ - public class Gnonograms.Block { public int length; public bool is_complete; diff --git a/libcore/utils.vala b/libcore/utils.vala index b7696fd..230ce09 100644 --- a/libcore/utils.vala +++ b/libcore/utils.vala @@ -1,20 +1,8 @@ -/* utils.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ namespace Gnonograms.Utils { public static string[] remove_blank_lines (string[] sa) { diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 87c0e49..44f976b 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -1,22 +1,9 @@ -/* CellGrid.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ - public class Gnonograms.CellGrid : Gtk.DrawingArea { public signal void start_drawing (uint button, Gdk.ModifierType state, bool double_click); public signal void stop_drawing (); diff --git a/libcore/widgets/Clue.vala b/libcore/widgets/Clue.vala index 6f5d660..42cfbe6 100644 --- a/libcore/widgets/Clue.vala +++ b/libcore/widgets/Clue.vala @@ -1,22 +1,9 @@ -/* Label.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ - class Gnonograms.Clue : Object { public Gtk.Label label { get; construct; } public unowned ClueBox cluebox { get; construct; } diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index 4edadd8..88bfe8e 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -1,22 +1,9 @@ - /* - * Copyright (C) 2010 - 2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ - public class Gnonograms.ClueBox : Gtk.Box { public unowned View view { get; construct; } public int font_size { get; private set; } diff --git a/src/Application.vala b/src/Application.vala index f62f157..b977977 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -1,20 +1,8 @@ -/* Application.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ namespace Gnonograms { public GLib.Settings saved_state; diff --git a/src/Controller.vala b/src/Controller.vala index 5c9b7f1..8b64a55 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -1,20 +1,8 @@ -/* Controller.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ public class Gnonograms.Controller : GLib.Object { diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 13eb93f..3ca8d97 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -1,21 +1,9 @@ -/* AppPopover.vala -* Copyright (C) 2010-2022 Jeremy Wootten -* - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -* -* Author: Jeremy Wootten -*/ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten + * + * Authored by: Jeremy Wootten + */ public class Gnonograms.AppPopover : Gtk.Popover { public signal void apply_settings (); diff --git a/src/HeaderBar/HeaderButton.vala b/src/HeaderBar/HeaderButton.vala index 714cf2e..bc1cd15 100644 --- a/src/HeaderBar/HeaderButton.vala +++ b/src/HeaderBar/HeaderButton.vala @@ -1,22 +1,9 @@ -/* View.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ - public class Gnonograms.HeaderButton : Gtk.Button { public HeaderButton (string icon_name, string action_name, string text) { Object ( diff --git a/src/HeaderBar/ProgressIndicator.vala b/src/HeaderBar/ProgressIndicator.vala index f0148af..8f99de3 100644 --- a/src/HeaderBar/ProgressIndicator.vala +++ b/src/HeaderBar/ProgressIndicator.vala @@ -1,21 +1,9 @@ -/* Copyright (C) 2010-2022 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ - public class Gnonograms.ProgressIndicator : Gtk.Box { private Gtk.Spinner spinner; private Gtk.Button cancel_button; diff --git a/src/HeaderBar/RestartButton.vala b/src/HeaderBar/RestartButton.vala index 7ad25c7..eb3784d 100644 --- a/src/HeaderBar/RestartButton.vala +++ b/src/HeaderBar/RestartButton.vala @@ -1,22 +1,9 @@ /* - * Copyright (C) 2010-2022 Jeremy Wootten + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ - private class Gnonograms.RestartButton : Gnonograms.HeaderButton { public bool restart_destructive { get; set; } diff --git a/src/View.vala b/src/View.vala index 3fdbb59..88aa478 100644 --- a/src/View.vala +++ b/src/View.vala @@ -1,20 +1,8 @@ -/* View.vala - * Copyright (C) 2010-2022 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ public class Gnonograms.View : Gtk.ApplicationWindow { diff --git a/src/services/RandomGameGenerator.vala b/src/services/RandomGameGenerator.vala index bdcbae6..2609908 100644 --- a/src/services/RandomGameGenerator.vala +++ b/src/services/RandomGameGenerator.vala @@ -1,20 +1,8 @@ -/* SimpleRandomGameGenerator.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ public class Gnonograms.SimpleRandomGameGenerator : Object { public RandomPatternGenerator pattern_gen { get; construct; } diff --git a/src/services/RandomPatternGenerator.vala b/src/services/RandomPatternGenerator.vala index 29a38aa..dc5df0c 100644 --- a/src/services/RandomPatternGenerator.vala +++ b/src/services/RandomPatternGenerator.vala @@ -1,20 +1,8 @@ -/* RandomPatternGenerator.vala - * Copyright (C) 2010-2021 Jeremy Wootten +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten * - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - * - * Author: Jeremy Wootten + * Authored by: Jeremy Wootten */ public class Gnonograms.RandomPatternGenerator : Object { public Dimensions dimensions { get; construct; } From 2653876a37d7414c5f66b018c5c7734a53a81371 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 21 Feb 2024 12:40:50 +0000 Subject: [PATCH 054/142] Discontinue zoom action --- src/View.vala | 58 +++------------------------------------------------ 1 file changed, 3 insertions(+), 55 deletions(-) diff --git a/src/View.vala b/src/View.vala index 88aa478..54ea04b 100644 --- a/src/View.vala +++ b/src/View.vala @@ -11,7 +11,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private const int GRID_BORDER = 6; private const int GRID_COLUMN_SPACING = 6; private const double TYPICAL_MAX_BLOCKS_RATIO = 0.3; - private const double ZOOM_RATIO = 0.05; + private const int WINDOW_INCREMENT = 8; private const uint PROGRESS_DELAY_MSEC = 500; private const string PAINT_FILL_ACCEL = "f"; // Must be lower case private const string PAINT_EMPTY_ACCEL = "e"; // Must be lower case @@ -21,8 +21,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public const string ACTION_PREFIX = ACTION_GROUP + "."; public const string ACTION_UNDO = "action-undo"; public const string ACTION_REDO = "action-redo"; - public const string ACTION_ZOOM_IN = "action-zoom-in"; - public const string ACTION_ZOOM_OUT = "action-zoom-out"; + // public const string ACTION_ZOOM_IN = "action-zoom-in"; + // public const string ACTION_ZOOM_OUT = "action-zoom-out"; public const string ACTION_CURSOR_UP = "action-cursor_up"; public const string ACTION_CURSOR_DOWN = "action-cursor_down"; public const string ACTION_CURSOR_LEFT = "action-cursor_left"; @@ -50,8 +50,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private static GLib.ActionEntry [] view_action_entries = { {ACTION_UNDO, action_undo}, {ACTION_REDO, action_redo}, - {ACTION_ZOOM_IN, action_zoom_in}, - {ACTION_ZOOM_OUT, action_zoom_out}, {ACTION_CURSOR_UP, action_cursor_up}, {ACTION_CURSOR_DOWN, action_cursor_down}, {ACTION_CURSOR_LEFT, action_cursor_left}, @@ -144,11 +142,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { action_accelerators.set (ACTION_CURSOR_DOWN, "Down"); action_accelerators.set (ACTION_CURSOR_LEFT, "Left"); action_accelerators.set (ACTION_CURSOR_RIGHT, "Right"); - action_accelerators.set (ACTION_ZOOM_IN, "plus"); - action_accelerators.set (ACTION_ZOOM_IN, "equal"); - action_accelerators.set (ACTION_ZOOM_IN, "KP_Add"); - action_accelerators.set (ACTION_ZOOM_OUT, "minus"); - action_accelerators.set (ACTION_ZOOM_OUT, "KP_Subtract"); action_accelerators.set (ACTION_SETTING_MODE, "1"); action_accelerators.set (ACTION_SOLVING_MODE, "2"); action_accelerators.set (ACTION_GENERATING_MODE, "3"); @@ -380,30 +373,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { main_grid.attach (column_clue_box, 1, 0, 1, 1); /* Clues for columns */ main_grid.attach (cell_grid, 1, 1, 1, 1); - var scroll_controller = new Gtk.EventControllerScroll ( - Gtk.EventControllerScrollFlags.VERTICAL | - Gtk.EventControllerScrollFlags.DISCRETE - ); - main_grid.add_controller (scroll_controller); - scroll_controller.scroll.connect ((dx, dy) => { - var modifiers = scroll_controller.get_current_event_state (); - if ((modifiers & Gdk.ModifierType.CONTROL_MASK) > 0) { - Idle.add (() => { - if (dy > 0.0) { - action_zoom_in (); - } else { - action_zoom_out (); - } - - return Source.REMOVE; - }); - - return Gdk.EVENT_STOP; - } - return Gdk.EVENT_PROPAGATE; - }); var key_controller = new Gtk.EventControllerKey (); main_grid.add_controller (key_controller); @@ -799,29 +770,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { controller.save_game_as.begin (); } - private void action_zoom_in () { - change_cell_size (true); - } - private void action_zoom_out () { - change_cell_size (false); - } - - private void change_cell_size (bool increase) { - var delta = double.max (ZOOM_RATIO * cell_size, 1.0); - if (increase) { - cell_size += (int)delta; - } else { - cell_size -= (int)delta; - //FIXME This is a hack to fix redrawing the window when the grid gets smaller. For some - //FIXME This is a hack to fix redrawing the window when the grid gets smaller. For some - //reason the window only properly redraws when the size increases. Review for later versions of Gtk4/Elementary - cell_size -= (int)delta; - Idle.add (() => { - cell_size += (int)delta; - return Source.REMOVE; - }); - } - } private void action_check_errors () { if (controller.rewind_until_correct () == 0) { From ec031ccb85f74043f428364e4834b4e297e571b5 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 24 Oct 2024 19:36:59 +0000 Subject: [PATCH 055/142] Start simplifying --- libcore/widgets/Cellgrid.vala | 52 +++++++---- src/Controller.vala | 18 ++++ src/HeaderBar/AppPopover.vala | 169 +++++++++++++++++----------------- src/View.vala | 48 +++------- 4 files changed, 149 insertions(+), 138 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 44f976b..002ede6 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -106,7 +106,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { }); view.notify["cell-size"].connect (size_updated); - view.controller.notify["dimensions"].connect (size_updated); + // view.controller.notify["dimensions"].connect (size_updated); view.controller.notify["game-state"].connect (() => { on_game_state_changed (); }); @@ -119,18 +119,28 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { }); size_updated (); + + settings.changed["filled-color"].connect (set_colors); + settings.changed["empty-color"].connect (set_colors); + settings.changed["rows"].connect (size_updated); + settings.changed["columns"].connect (size_updated); } public void set_colors () { - var setting = (int) GameState.SETTING; - colors[setting, (int) CellState.UNKNOWN].parse (Gnonograms.UNKNOWN_COLOR); - colors[setting, (int) CellState.EMPTY].parse (Gnonograms.SETTING_EMPTY_COLOR); - colors[setting, (int) CellState.FILLED].parse (Gnonograms.SETTING_FILLED_COLOR); - setting = (int) GameState.SOLVING; - colors[setting, (int) CellState.UNKNOWN].parse (Gnonograms.UNKNOWN_COLOR); - colors[setting, (int) CellState.EMPTY].parse (settings.get_string ("empty-color")); - colors[setting, (int) CellState.FILLED].parse (settings.get_string ("filled-color")); - on_game_state_changed (); + // Ensure settings have updated + Idle.add (() => { + var setting = (int) GameState.SETTING; + colors[setting, (int) CellState.UNKNOWN].parse (Gnonograms.UNKNOWN_COLOR); + colors[setting, (int) CellState.EMPTY].parse (Gnonograms.SETTING_EMPTY_COLOR); + colors[setting, (int) CellState.FILLED].parse (Gnonograms.SETTING_FILLED_COLOR); + setting = (int) GameState.SOLVING; + colors[setting, (int) CellState.UNKNOWN].parse (Gnonograms.UNKNOWN_COLOR); + colors[setting, (int) CellState.EMPTY].parse (settings.get_string ("empty-color")); + colors[setting, (int) CellState.FILLED].parse (settings.get_string ("filled-color")); + on_game_state_changed (); + queue_draw (); + return Source.REMOVE; + }); } private void on_game_state_changed () { @@ -142,14 +152,20 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } private void size_updated () { - rows = (int)view.controller.dimensions.height; - cols = (int)view.controller.dimensions.width; - cell_width = view.cell_size; - cell_height = view.cell_size; - /* Cause refresh of existing pattern */ - highlight_pattern = new CellPattern.highlight (cell_width, cell_height); - content_width = cols * view.cell_size + (int)MINOR_GRID_LINE_WIDTH; - content_height = rows * view.cell_size + (int)MINOR_GRID_LINE_WIDTH; + Idle.add (() => { + rows = (int)settings.get_uint ("rows"); + cols = (int)settings.get_uint ("columns"); + // rows = (int)view.controller.dimensions.height; + // cols = (int)view.controller.dimensions.width; + cell_width = view.cell_size; + cell_height = view.cell_size; + /* Cause refresh of existing pattern */ + highlight_pattern = new CellPattern.highlight (cell_width, cell_height); + content_width = cols * view.cell_size + (int)MINOR_GRID_LINE_WIDTH; + content_height = rows * view.cell_size + (int)MINOR_GRID_LINE_WIDTH; + queue_draw (); + return Source.REMOVE; + }); } private void draw_func (Gtk.DrawingArea drawing_area, Cairo.Context cr, int x, int y) { diff --git a/src/Controller.vala b/src/Controller.vala index 8b64a55..4883c8a 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -11,6 +11,24 @@ public class Gnonograms.Controller : GLib.Object { public Gtk.Window window { get { return (Gtk.Window)view;}} public GameState game_state { get; set; } public Dimensions dimensions { get; set; } + public uint rows { + get { + return dimensions.height; + } + + set { + dimensions.height = value.clamp (5, 50); + } + } + public uint columns { + get { + return dimensions.width; + } + + set { + dimensions.width = value.clamp (5, 50); + } + } public Difficulty generator_grade { get; set; } public string game_name { get; set; } diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 3ca8d97..974d0f4 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -14,79 +14,62 @@ public class Gnonograms.AppPopover : Gtk.Popover { private Gtk.ColorDialogButton filled_color_setting; private Gtk.ColorDialogButton empty_color_setting; - public Difficulty grade { - get { - return (Difficulty) (grade_setting.get_selected ()); - } - - set { - grade_setting.set_selected ( - ((uint) value).clamp ( - MIN_GRADE, - Difficulty.MAXIMUM - ) - ); - } + // public Difficulty grade { + // get { + // return (Difficulty) (grade_setting.get_selected ()); + // } + + // set { + // grade_setting.set_selected ( + // ((uint) value).clamp ( + // MIN_GRADE, + // Difficulty.MAXIMUM + // ) + // ); + // } + // } + + // public uint rows { + // get { + // return (uint)(row_setting.@value); + // } + + // set { + // row_setting.@value = value; + // } + // } + + // public uint columns { + // get { + // return (uint)(column_setting.@value); + // } + + // set { + // column_setting.@value = value; + // } + // } + + // public string title { + // get { + // return title_setting.text; + // } + + // set { + // title_setting.text = value; + // } + // } + + public string filled_color { get; set; } + public string empty_color { get; set; } + + + public Controller controller { get; construct; } + public AppPopover (Controller controller) { + Object ( + controller: controller + ); } - - public uint rows { - get { - return (uint)(row_setting.@value); - } - - set { - row_setting.@value = value; - } - } - - public uint columns { - get { - return (uint)(column_setting.@value); - } - - set { - column_setting.@value = value; - } - } - - public string title { - get { - return title_setting.text; - } - - set { - title_setting.text = value; - } - } - - public string filled_color { - owned get { - return filled_color_setting.get_rgba ().to_string (); - } - - set { - Gdk.RGBA color = {}; - if (color.parse (value)) { - filled_color_setting.set_rgba (color); - } - } - } - - public string empty_color { - owned get { - return empty_color_setting.get_rgba ().to_string (); - } - - set { - Gdk.RGBA color = {}; - if (color.parse (value)) { - empty_color_setting.set_rgba (color); - } - } - } - construct { - autohide = false; grade_setting = new Gtk.DropDown.from_strings (Difficulty.all_human ()); row_setting = new Gtk.SpinButton ( @@ -136,25 +119,37 @@ public class Gnonograms.AppPopover : Gtk.Popover { settings_grid.attach (new Gtk.Label (_("Empty Color:")), 0, 5, 1); settings_grid.attach (empty_color_setting, 1, 5, 1); - var cancel_button = new Gtk.Button.with_label (_("Cancel")); - var apply_button = new Gtk.Button.with_label (_("Apply")); - default_widget = apply_button; - cancel_button.clicked.connect (() => { - hide (); - }); - apply_button.clicked.connect (() => { - apply_settings (); - hide (); - }); - var popover_actions = new Gtk.ActionBar (); - popover_actions.pack_start (cancel_button); - popover_actions.pack_end (apply_button); - var main_widget = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); - // main_grid.append (size_grid); main_widget.append (settings_grid); - main_widget.append (popover_actions); child = main_widget; + + // Could not get bind with mapping to work with RGBA property + filled_color_setting.notify["rgba"].connect (() => { + var color = filled_color_setting.get_rgba (); + filled_color = color.to_string (); + }); + notify["filled-color"].connect (() => { + var color = Gdk.RGBA () {}; + color.parse (filled_color); + filled_color_setting.set_rgba (color); + }); + + empty_color_setting.notify["rgba"].connect (() => { + var color = empty_color_setting.get_rgba (); + empty_color = color.to_string (); + }); + notify["empty-color"].connect (() => { + var color = Gdk.RGBA () {}; + color.parse (empty_color); + empty_color_setting.set_rgba (color); + }); + + settings.bind ("grade", grade_setting, "selected", DEFAULT); + settings.bind ("filled-color", this, "filled-color", DEFAULT); + settings.bind ("empty-color", this, "empty-color", DEFAULT); + settings.bind ("rows", this, "rows", DEFAULT); + settings.bind ("columns", column_setting, "columns", DEFAULT); + settings.bind ("rows", row_setting, "value", DEFAULT); } } diff --git a/src/View.vala b/src/View.vala index 54ea04b..95dd49a 100644 --- a/src/View.vala +++ b/src/View.vala @@ -232,55 +232,37 @@ public class Gnonograms.View : Gtk.ApplicationWindow { ACTION_PREFIX + ACTION_HINT, _("Suggest next move") ); - auto_solve_button = new HeaderButton ( - "system", - ACTION_PREFIX + ACTION_SOLVE, - _("Solve by Computer") - ); + // auto_solve_button = new HeaderButton ( + // "system", + // ACTION_PREFIX + ACTION_SOLVE, + // _("Solve by Computer") + // ); generate_button = new HeaderButton ( "list-add", ACTION_PREFIX + ACTION_GENERATING_MODE, _("Generate New Puzzle") ); - var app_popover = new AppPopover () { + + var app_popover = new AppPopover (controller) { has_arrow = false }; - app_popover.apply_settings.connect (() => { - controller.generator_grade = app_popover.grade; - controller.dimensions = {app_popover.columns, app_popover.rows}; - controller.game_name = app_popover.title; // Must come after changing dimensions - settings.set_string ("filled-color", app_popover.filled_color); - settings.set_string ("empty-color", app_popover.empty_color); - // Wait for settings to update - Idle.add (() => { - cell_grid.set_colors (); - cell_grid.queue_draw (); - return Source.REMOVE; - }); - }); - app_popover.show.connect (() => { /* Allow parent to set values first */ - app_popover.grade = controller.generator_grade; - app_popover.rows = controller.dimensions.height; - app_popover.columns = controller.dimensions.width; - app_popover.title = controller.game_name; - app_popover.filled_color = settings.get_string ("filled-color"); - app_popover.empty_color = settings.get_string ("empty-color"); - }); - var menu_image = new Gtk.Image.from_icon_name ("open-menu") { - pixel_size = 32 - }; + + // var menu_image = new Gtk.Image.from_icon_name ("open-menu") { + // pixel_size = 32 + // }; menu_button = new Gtk.MenuButton () { tooltip_markup = Granite.markup_accel_tooltip ( app.get_accels_for_action ( ACTION_PREFIX + ACTION_OPTIONS), _("Options") ), - child = menu_image, - has_frame = false + icon_name = "open-menu-symbolic", + can_focus = false, + valign = Gtk.Align.CENTER, + popover = (new AppPopover (controller)) }; - menu_button.set_popover (app_popover); // Unable to set markup on Granite.ModeSwitch so fake a Granite accelerator tooltip for now. mode_switch = new Granite.ModeSwitch.from_icon_name ( From ed2f425f71f3d41357ccb890e327c5e1d6c58cbc Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 4 Nov 2024 17:21:57 +0000 Subject: [PATCH 056/142] Continue simplifying, add basic preferences dialog --- meson.build | 18 +-- src/Controller.vala | 41 +++---- src/Dialogs/PreferencesDialog.vala | 52 +++++++++ src/HeaderBar/AppPopover.vala | 172 +++++++++-------------------- src/View.vala | 39 +------ 5 files changed, 127 insertions(+), 195 deletions(-) create mode 100644 src/Dialogs/PreferencesDialog.vala diff --git a/meson.build b/meson.build index e103736..fb355da 100644 --- a/meson.build +++ b/meson.build @@ -16,13 +16,6 @@ endif i18n = import ('i18n') gnome = import('gnome') -# Currently no custom resources for Gtk4 version. Thinking-head icon not used -# gresource = gnome.compile_resources( -# 'gresource', -# 'data/gresource.xml', -# source_dir: 'data' -# ) - config_data = configuration_data() config_data.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), get_option('localedir'))) config_data.set_quoted('GETTEXT_PACKAGE', meson.project_name()) @@ -54,6 +47,7 @@ executable ( 'src/HeaderBar/AppPopover.vala', 'src/services/RandomPatternGenerator.vala', 'src/services/RandomGameGenerator.vala', + 'src/Dialogs/PreferencesDialog.vala', 'libcore/widgets/Cluebox.vala', 'libcore/widgets/Clue.vala', 'libcore/widgets/Cellgrid.vala', @@ -69,18 +63,8 @@ executable ( 'libcore/Enums.vala', 'libcore/Structs.vala', 'libcore/Constants.vala', -# <<<<<<< HEAD -# config_file, - -# dependencies : [ -# dependency('granite-7'), -# dependency('libadwaita-1') -# ], - -# ======= config_file, dependencies : gnonogram_deps, -# >>>>>>> master install: true ) diff --git a/src/Controller.vala b/src/Controller.vala index 4883c8a..cbb1252 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -10,25 +10,13 @@ public class Gnonograms.Controller : GLib.Object { public Gtk.Window window { get { return (Gtk.Window)view;}} public GameState game_state { get; set; } - public Dimensions dimensions { get; set; } - public uint rows { + public Dimensions dimensions { get { - return dimensions.height; - } - - set { - dimensions.height = value.clamp (5, 50); - } - } - public uint columns { - get { - return dimensions.width; - } - - set { - dimensions.width = value.clamp (5, 50); + return {columns, rows}; } } + public uint rows { get; set;} + public uint columns { get; set; } public Difficulty generator_grade { get; set; } public string game_name { get; set; } @@ -66,7 +54,11 @@ public class Gnonograms.Controller : GLib.Object { } }); - notify["dimensions"].connect (() => { + notify["columns"].connect (() => { + solver = new Solver (dimensions); + game_name = _(UNTITLED_NAME); + }); + notify["rows"].connect (() => { solver = new Solver (dimensions); game_name = _(UNTITLED_NAME); }); @@ -246,12 +238,11 @@ public class Gnonograms.Controller : GLib.Object { } private void restore_dimensions () { - dimensions = { 15, 10 }; /* Fallback dimensions */ + columns = 15; + rows = 10; if (settings != null) { - dimensions = { - settings.get_uint ("columns").clamp (10, 50), - settings.get_uint ("rows").clamp (10, 50) - }; + columns = settings.get_uint ("columns").clamp (10, 50); + rows = settings.get_uint ("rows").clamp (10, 50); } } @@ -386,7 +377,8 @@ public class Gnonograms.Controller : GLib.Object { reader.err_msg = (_("Dimensions too small")); return false; } else { - dimensions = {reader.cols, reader.rows}; + columns = reader.cols; + rows = reader.rows; } } else { reader.err_msg = (_("Dimensions missing")); @@ -645,4 +637,7 @@ public class Gnonograms.Controller : GLib.Object { view.end_working (); } + + public void increase_fontsize () {} + public void decrease_fontsize () {} } diff --git a/src/Dialogs/PreferencesDialog.vala b/src/Dialogs/PreferencesDialog.vala new file mode 100644 index 0000000..4f374c9 --- /dev/null +++ b/src/Dialogs/PreferencesDialog.vala @@ -0,0 +1,52 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten + * + * Authored by: Jeremy Wootten + */ + +public class Gnonograms.Dialogs.Preferences : Granite.Dialog { + + public Preferences (Gtk.Window? parent) { + Object ( + title: _("Preferences"), + transient_for: parent + ); + } + + construct { + var grade_setting = new Gtk.DropDown.from_strings ( Difficulty.all_human ()); + var row_setting = new Gtk.SpinButton ( + new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), + 5.0, + 0 + ) { + snap_to_ticks = true, + orientation = Gtk.Orientation.HORIZONTAL, + width_chars = 3, + }; + + var column_setting = new Gtk.SpinButton ( + new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), + 5.0, + 0 + ) { + snap_to_ticks = true, + orientation = Gtk.Orientation.HORIZONTAL, + width_chars = 3, + }; + + //TODO Add Clue help switch + + var main_box = new Gtk.Box (VERTICAL, 12); + main_box.append (grade_setting); + main_box.append (row_setting); + main_box.append (column_setting); + + get_content_area ().append (main_box); + add_button (_("Close"), Gtk.ResponseType.APPLY); + + settings.bind ("columns", column_setting, "value", DEFAULT); + settings.bind ("rows", row_setting, "value", DEFAULT); + } +} diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 974d0f4..996a883 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -5,60 +5,13 @@ * Authored by: Jeremy Wootten */ public class Gnonograms.AppPopover : Gtk.Popover { - public signal void apply_settings (); - - private Gtk.DropDown grade_setting; private Gtk.SpinButton row_setting; private Gtk.SpinButton column_setting; private Gtk.Entry title_setting; private Gtk.ColorDialogButton filled_color_setting; private Gtk.ColorDialogButton empty_color_setting; - // public Difficulty grade { - // get { - // return (Difficulty) (grade_setting.get_selected ()); - // } - - // set { - // grade_setting.set_selected ( - // ((uint) value).clamp ( - // MIN_GRADE, - // Difficulty.MAXIMUM - // ) - // ); - // } - // } - - // public uint rows { - // get { - // return (uint)(row_setting.@value); - // } - - // set { - // row_setting.@value = value; - // } - // } - - // public uint columns { - // get { - // return (uint)(column_setting.@value); - // } - - // set { - // column_setting.@value = value; - // } - // } - - // public string title { - // get { - // return title_setting.text; - // } - - // set { - // title_setting.text = value; - // } - // } - + public Difficulty grade { get; set; } public string filled_color { get; set; } public string empty_color { get; set; } @@ -70,86 +23,67 @@ public class Gnonograms.AppPopover : Gtk.Popover { ); } construct { - grade_setting = new Gtk.DropDown.from_strings (Difficulty.all_human ()); + var zoom_out_button = new Gtk.Button.from_icon_name ("zoom-out-symbolic"); + zoom_out_button.tooltip_markup = Granite.markup_accel_tooltip ( + {"minus"}, + _("Zoom Out") + ); + zoom_out_button.clicked.connect (() => { + controller.decrease_fontsize (); + }); - row_setting = new Gtk.SpinButton ( - new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), - 5.0, - 0 - ) { - snap_to_ticks = true, - orientation = Gtk.Orientation.HORIZONTAL, - width_chars = 3, - }; + var zoom_in_button = new Gtk.Button.from_icon_name ("zoom-in-symbolic"); + zoom_in_button.tooltip_markup = Granite.markup_accel_tooltip ( + {"plus"}, + _("Zoom In") + ); + zoom_in_button.clicked.connect (() => { + controller.increase_fontsize (); + }); - column_setting = new Gtk.SpinButton ( - new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), - 5.0, - 0 - ) { - snap_to_ticks = true, - orientation = Gtk.Orientation.HORIZONTAL, - width_chars = 3, + var font_size_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) { + homogeneous = true, + hexpand = true, + margin_top = 12, + margin_start = 12, + margin_end = 12, }; + font_size_box.add_css_class (Granite.STYLE_CLASS_LINKED); + font_size_box.append (zoom_out_button); + font_size_box.append (zoom_in_button); - title_setting = new Gtk.Entry () { - placeholder_text = _("Enter title of game here") + var title_entry = new Gtk.Entry () { + placeholder_text = _("Enter title of game here"), + margin_top = 12, }; - filled_color_setting = new Gtk.ColorDialogButton (new Gtk.ColorDialog ()); - empty_color_setting = new Gtk.ColorDialogButton (new Gtk.ColorDialog ()); - - var settings_grid = new Gtk.Grid () { - orientation = Gtk.Orientation.VERTICAL, - row_spacing = 12, - column_spacing = 12, - margin_start = margin_end = margin_top = 12, - margin_bottom = 24 + var preferences_button = new Gtk.Button () { + margin_top = 3, + margin_bottom = 3 }; - settings_grid.attach (new Gtk.Label (_("Name:")), 0, 0, 1); - settings_grid.attach (title_setting, 1, 0, 3); - settings_grid.attach (new Gtk.Label (_("Difficulty:")), 0, 1, 1); - settings_grid.attach (grade_setting, 1, 1, 3); - settings_grid.attach (new Gtk.Label (_("Rows:")), 0, 2, 1); - settings_grid.attach (row_setting, 1, 2, 1); - settings_grid.attach (new Gtk.Label (_("Columns:")), 0, 3, 1); - settings_grid.attach (column_setting, 1, 3, 1); - settings_grid.attach (new Gtk.Label (_("Filled Color:")), 0, 4, 1); - settings_grid.attach (filled_color_setting, 1, 4, 1); - settings_grid.attach (new Gtk.Label (_("Empty Color:")), 0, 5, 1); - settings_grid.attach (empty_color_setting, 1, 5, 1); - - var main_widget = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); - main_widget.append (settings_grid); - - child = main_widget; - - // Could not get bind with mapping to work with RGBA property - filled_color_setting.notify["rgba"].connect (() => { - var color = filled_color_setting.get_rgba (); - filled_color = color.to_string (); - }); - notify["filled-color"].connect (() => { - var color = Gdk.RGBA () {}; - color.parse (filled_color); - filled_color_setting.set_rgba (color); + preferences_button.add_css_class (Granite.STYLE_CLASS_FLAT); + preferences_button.child = new Gtk.Label (_("Preferences")) { + xalign = 0.0f + }; + preferences_button.clicked.connect (() => { + popdown (); + var dialog = new Dialogs.Preferences ((Gtk.Window)get_ancestor (typeof (Gtk.Window))); + dialog.response.connect (() => { + dialog.destroy (); + }); + dialog.present (); }); - empty_color_setting.notify["rgba"].connect (() => { - var color = empty_color_setting.get_rgba (); - empty_color = color.to_string (); - }); - notify["empty-color"].connect (() => { - var color = Gdk.RGBA () {}; - color.parse (empty_color); - empty_color_setting.set_rgba (color); - }); + var menu_separator = new Gtk.Separator (Gtk.Orientation.HORIZONTAL) { + margin_top = 6 + }; + + var settings_box = new Gtk.Box (VERTICAL, 0); + settings_box.append (font_size_box); + settings_box.append (title_entry); + settings_box.append (menu_separator); + settings_box.append (preferences_button); - settings.bind ("grade", grade_setting, "selected", DEFAULT); - settings.bind ("filled-color", this, "filled-color", DEFAULT); - settings.bind ("empty-color", this, "empty-color", DEFAULT); - settings.bind ("rows", this, "rows", DEFAULT); - settings.bind ("columns", column_setting, "columns", DEFAULT); - settings.bind ("rows", row_setting, "value", DEFAULT); + child = settings_box; } } diff --git a/src/View.vala b/src/View.vala index 95dd49a..d8ff2a1 100644 --- a/src/View.vala +++ b/src/View.vala @@ -244,14 +244,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { ); - var app_popover = new AppPopover (controller) { - has_arrow = false - }; - + var app_popover = new AppPopover (controller); - // var menu_image = new Gtk.Image.from_icon_name ("open-menu") { - // pixel_size = 32 - // }; menu_button = new Gtk.MenuButton () { tooltip_markup = Granite.markup_accel_tooltip ( app.get_accels_for_action ( @@ -259,9 +253,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { _("Options") ), icon_name = "open-menu-symbolic", - can_focus = false, valign = Gtk.Align.CENTER, - popover = (new AppPopover (controller)) + popover = new AppPopover (controller) }; // Unable to set markup on Granite.ModeSwitch so fake a Granite accelerator tooltip for now. @@ -299,8 +292,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { header_bar = new Gtk.HeaderBar () { show_title_buttons = true, - title_widget = progress_stack, - can_focus = false + title_widget = progress_stack }; header_bar.add_css_class ("gnonograms-header"); header_bar.pack_start (load_game_button); @@ -355,9 +347,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { main_grid.attach (column_clue_box, 1, 0, 1, 1); /* Clues for columns */ main_grid.attach (cell_grid, 1, 1, 1, 1); - - - var key_controller = new Gtk.EventControllerKey (); main_grid.add_controller (key_controller); @@ -441,10 +430,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { update_all_labels_completeness (); }); - controller.notify["dimensions"].connect (() => { - calc_cell_size (); - }); - cell_grid.leave.connect (() => { row_clue_box.unhighlight_all (); column_clue_box.unhighlight_all (); @@ -472,24 +457,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { }); cell_grid.stop_drawing.connect (stop_painting); - notify["default-width"].connect (calc_cell_size); - notify["default-height"].connect (calc_cell_size); - } - - private void calc_cell_size () { - // Update cell-size if required to fit in window - var n_cols = controller.dimensions.width; - var n_rows = controller.dimensions.height; - - - var grid_width = this.default_width; - int header_height, nat; - header_bar.measure (VERTICAL, grid_width, out header_height, out nat, null, null); - var grid_height = this.default_height - header_height; - var max_cell_width = grid_width / (n_cols * (1.25)); - - var max_cell_height = grid_height / (n_rows * (1.35)); - cell_size = (int) (double.min (max_cell_width, max_cell_height)).clamp (8, 128); } public string[] get_clues (bool is_column) { From 6555bfe296b493f8b78fe6f84f123df9836ab074 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 4 Nov 2024 18:28:27 +0000 Subject: [PATCH 057/142] Add load, save save_as buttons to AppPopover --- libcore/Model.vala | 33 +++++++---- src/Application.vala | 32 ++++++++++- src/Controller.vala | 4 +- src/HeaderBar/AppPopover.vala | 51 ++++++++--------- src/View.vala | 103 +++++++++++++++------------------- 5 files changed, 126 insertions(+), 97 deletions(-) diff --git a/libcore/Model.vala b/libcore/Model.vala index 72bf138..ac0d8a8 100644 --- a/libcore/Model.vala +++ b/libcore/Model.vala @@ -23,8 +23,16 @@ public class Gnonograms.Model : GLib.Object { private My2DCellArray solution_data { get; set; } private My2DCellArray working_data { get; set; } - private uint rows = 0; - private uint cols = 0; + private uint rows { + get { + return controller.rows; + } + } + private uint cols { + get { + return controller.columns; + } + } public Model (Controller controller) { Object ( @@ -33,19 +41,24 @@ public class Gnonograms.Model : GLib.Object { } construct { - controller.notify["dimensions"].connect (() => { - rows = controller.dimensions.height; - cols = controller.dimensions.width; - solution_data = new My2DCellArray (controller.dimensions, CellState.EMPTY); - working_data = new My2DCellArray (controller.dimensions, CellState.UNKNOWN); - changed (); - }); - + make_data_arrays (); + controller.notify["rows"].connect (on_changed_dimensions); + controller.notify["columns"].connect (on_changed_dimensions); controller.notify["game-state"].connect (() => { changed (); }); } + private void on_changed_dimensions () { + make_data_arrays (); + changed (); + } + + private void make_data_arrays () { + solution_data = new My2DCellArray (controller.dimensions, CellState.EMPTY); + working_data = new My2DCellArray (controller.dimensions, CellState.UNKNOWN); + } + public int count_errors () { CellState cs; int count = 0; diff --git a/src/Application.vala b/src/Application.vala index b977977..9e217e6 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -5,10 +5,38 @@ * Authored by: Jeremy Wootten */ namespace Gnonograms { + public const string ACTION_GROUP = "win"; + public const string ACTION_PREFIX = ACTION_GROUP + "."; + public const string ACTION_UNDO = "action-undo"; + public const string ACTION_REDO = "action-redo"; + // public const string ACTION_ZOOM_IN = "action-zoom-in"; + // public const string ACTION_ZOOM_OUT = "action-zoom-out"; + public const string ACTION_CURSOR_UP = "action-cursor_up"; + public const string ACTION_CURSOR_DOWN = "action-cursor_down"; + public const string ACTION_CURSOR_LEFT = "action-cursor_left"; + public const string ACTION_CURSOR_RIGHT = "action-cursor_right"; + public const string ACTION_SETTING_MODE = "action-setting-mode"; + public const string ACTION_SOLVING_MODE = "action-solving-mode"; + public const string ACTION_GENERATING_MODE = "action-generating-mode"; + public const string ACTION_OPEN = "action-open"; + public const string ACTION_SAVE = "action-save"; + public const string ACTION_SAVE_AS = "action-save-as"; + public const string ACTION_PAINT_FILLED = "action-paint-filled"; + public const string ACTION_PAINT_EMPTY = "action-paint-empty"; + public const string ACTION_PAINT_UNKNOWN = "action-paint-unknown"; + public const string ACTION_CHECK_ERRORS = "action-check-errors"; + public const string ACTION_RESTART = "action-restart"; + public const string ACTION_SOLVE = "action-solve"; + public const string ACTION_HINT = "action-hint"; + public const string ACTION_OPTIONS = "action-options"; +#if WITH_DEBUGGING + public const string ACTION_DEBUG_ROW = "action-debug-row"; + public const string ACTION_DEBUG_COL = "action-debug-col"; +#endif public GLib.Settings saved_state; public GLib.Settings settings; - -public class App : Gtk.Application { + + public class App : Gtk.Application { private Controller controller; public App () { diff --git a/src/Controller.vala b/src/Controller.vala index cbb1252..f09e795 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -92,8 +92,8 @@ public class Gnonograms.Controller : GLib.Object { ); if (saved_state != null && settings != null) { - saved_state.bind ("mode", this, "game_state", SettingsBindFlags.DEFAULT); - settings.bind ("grade", this, "generator_grade", SettingsBindFlags.DEFAULT); + saved_state.bind ("mode", this, "game-state", SettingsBindFlags.DEFAULT); + settings.bind ("grade", this, "generator-grade", SettingsBindFlags.DEFAULT); settings.bind ("clue-help", view, "strikeout-complete", SettingsBindFlags.DEFAULT); } else { restore_settings (); diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 996a883..53daae9 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -5,17 +5,6 @@ * Authored by: Jeremy Wootten */ public class Gnonograms.AppPopover : Gtk.Popover { - private Gtk.SpinButton row_setting; - private Gtk.SpinButton column_setting; - private Gtk.Entry title_setting; - private Gtk.ColorDialogButton filled_color_setting; - private Gtk.ColorDialogButton empty_color_setting; - - public Difficulty grade { get; set; } - public string filled_color { get; set; } - public string empty_color { get; set; } - - public Controller controller { get; construct; } public AppPopover (Controller controller) { Object ( @@ -57,14 +46,11 @@ public class Gnonograms.AppPopover : Gtk.Popover { margin_top = 12, }; - var preferences_button = new Gtk.Button () { - margin_top = 3, - margin_bottom = 3 - }; - preferences_button.add_css_class (Granite.STYLE_CLASS_FLAT); - preferences_button.child = new Gtk.Label (_("Preferences")) { - xalign = 0.0f - }; + var load_game_button = new PopoverButton (_("Load"), ACTION_PREFIX + ACTION_OPEN); + var save_game_button = new PopoverButton (_("Save"), ACTION_PREFIX + ACTION_SAVE); + var save_as_game_button = new PopoverButton (_("Save to Different File"), ACTION_PREFIX + ACTION_SAVE_AS); + var preferences_button = new PopoverButton (_("Preferences")); + preferences_button.clicked.connect (() => { popdown (); var dialog = new Dialogs.Preferences ((Gtk.Window)get_ancestor (typeof (Gtk.Window))); @@ -74,16 +60,31 @@ public class Gnonograms.AppPopover : Gtk.Popover { dialog.present (); }); - var menu_separator = new Gtk.Separator (Gtk.Orientation.HORIZONTAL) { - margin_top = 6 - }; - - var settings_box = new Gtk.Box (VERTICAL, 0); + var settings_box = new Gtk.Box (VERTICAL, 3); settings_box.append (font_size_box); settings_box.append (title_entry); - settings_box.append (menu_separator); + settings_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); + settings_box.append (load_game_button); + settings_box.append (save_game_button); + settings_box.append (save_as_game_button); + settings_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); settings_box.append (preferences_button); child = settings_box; } + + private class PopoverButton : Gtk.Button { + public PopoverButton (string label, string? action_name = null) { + Object ( + child: new Gtk.Label (label) {xalign = 0.0f}, + action_name: action_name + ); + } + + construct { + margin_top = 3; + margin_bottom = 3; + add_css_class (Granite.STYLE_CLASS_FLAT); + } + } } diff --git a/src/View.vala b/src/View.vala index d8ff2a1..8bc7909 100644 --- a/src/View.vala +++ b/src/View.vala @@ -17,34 +17,34 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private const string PAINT_EMPTY_ACCEL = "e"; // Must be lower case private const string PAINT_UNKNOWN_ACCEL = "x"; // Must be lower case - public const string ACTION_GROUP = "win"; - public const string ACTION_PREFIX = ACTION_GROUP + "."; - public const string ACTION_UNDO = "action-undo"; - public const string ACTION_REDO = "action-redo"; - // public const string ACTION_ZOOM_IN = "action-zoom-in"; - // public const string ACTION_ZOOM_OUT = "action-zoom-out"; - public const string ACTION_CURSOR_UP = "action-cursor_up"; - public const string ACTION_CURSOR_DOWN = "action-cursor_down"; - public const string ACTION_CURSOR_LEFT = "action-cursor_left"; - public const string ACTION_CURSOR_RIGHT = "action-cursor_right"; - public const string ACTION_SETTING_MODE = "action-setting-mode"; - public const string ACTION_SOLVING_MODE = "action-solving-mode"; - public const string ACTION_GENERATING_MODE = "action-generating-mode"; - public const string ACTION_OPEN = "action-open"; - public const string ACTION_SAVE = "action-save"; - public const string ACTION_SAVE_AS = "action-save-as"; - public const string ACTION_PAINT_FILLED = "action-paint-filled"; - public const string ACTION_PAINT_EMPTY = "action-paint-empty"; - public const string ACTION_PAINT_UNKNOWN = "action-paint-unknown"; - public const string ACTION_CHECK_ERRORS = "action-check-errors"; - public const string ACTION_RESTART = "action-restart"; - public const string ACTION_SOLVE = "action-solve"; - public const string ACTION_HINT = "action-hint"; - public const string ACTION_OPTIONS = "action-options"; -#if WITH_DEBUGGING - public const string ACTION_DEBUG_ROW = "action-debug-row"; - public const string ACTION_DEBUG_COL = "action-debug-col"; -#endif +// public const string ACTION_GROUP = "win"; +// public const string ACTION_PREFIX = ACTION_GROUP + "."; +// public const string ACTION_UNDO = "action-undo"; +// public const string ACTION_REDO = "action-redo"; +// // public const string ACTION_ZOOM_IN = "action-zoom-in"; +// // public const string ACTION_ZOOM_OUT = "action-zoom-out"; +// public const string ACTION_CURSOR_UP = "action-cursor_up"; +// public const string ACTION_CURSOR_DOWN = "action-cursor_down"; +// public const string ACTION_CURSOR_LEFT = "action-cursor_left"; +// public const string ACTION_CURSOR_RIGHT = "action-cursor_right"; +// public const string ACTION_SETTING_MODE = "action-setting-mode"; +// public const string ACTION_SOLVING_MODE = "action-solving-mode"; +// public const string ACTION_GENERATING_MODE = "action-generating-mode"; +// public const string ACTION_OPEN = "action-open"; +// public const string ACTION_SAVE = "action-save"; +// public const string ACTION_SAVE_AS = "action-save-as"; +// public const string ACTION_PAINT_FILLED = "action-paint-filled"; +// public const string ACTION_PAINT_EMPTY = "action-paint-empty"; +// public const string ACTION_PAINT_UNKNOWN = "action-paint-unknown"; +// public const string ACTION_CHECK_ERRORS = "action-check-errors"; +// public const string ACTION_RESTART = "action-restart"; +// public const string ACTION_SOLVE = "action-solve"; +// public const string ACTION_HINT = "action-hint"; +// public const string ACTION_OPTIONS = "action-options"; +// #if WITH_DEBUGGING +// public const string ACTION_DEBUG_ROW = "action-debug-row"; +// public const string ACTION_DEBUG_COL = "action-debug-col"; +// #endif public static Gee.MultiMap action_accelerators; public static Gtk.Application app; private static GLib.ActionEntry [] view_action_entries = { @@ -103,9 +103,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private Gtk.Label title_label; private Gtk.Label grade_label; private Gtk.Button generate_button; - private Gtk.Button load_game_button; - private Gtk.Button save_game_button; - private Gtk.Button save_game_as_button; + // private Gtk.Button load_game_button; + // private Gtk.Button save_game_button; + // private Gtk.Button save_game_as_button; private Gtk.Button undo_button; private Gtk.Button redo_button; private Gtk.Button check_correct_button; @@ -189,21 +189,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { app.set_accels_for_action (ACTION_PREFIX + action, accels_array); } - load_game_button = new HeaderButton ( - "document-open", - ACTION_PREFIX + ACTION_OPEN, - _("Load Game") - ); - save_game_button = new HeaderButton ( - "document-save", - ACTION_PREFIX + ACTION_SAVE, - _("Save Game") - ); - save_game_as_button = new HeaderButton ( - "document-save-as", - ACTION_PREFIX + ACTION_SAVE_AS, - _("Save Game to Different File") - ); + undo_button = new HeaderButton ( "edit-undo-symbolic", ACTION_PREFIX + ACTION_UNDO, @@ -295,17 +281,18 @@ public class Gnonograms.View : Gtk.ApplicationWindow { title_widget = progress_stack }; header_bar.add_css_class ("gnonograms-header"); - header_bar.pack_start (load_game_button); - header_bar.pack_start (save_game_button); - header_bar.pack_start (save_game_as_button); + // header_bar.pack_start (load_game_button); + // header_bar.pack_start (save_game_button); + // header_bar.pack_start (save_game_as_button); + header_bar.pack_start (generate_button); header_bar.pack_start (restart_button); header_bar.pack_start (undo_button); header_bar.pack_start (redo_button); header_bar.pack_start (check_correct_button); header_bar.pack_end (menu_button); - header_bar.pack_end (generate_button); + // header_bar.pack_end (generate_button); header_bar.pack_end (mode_switch); - header_bar.pack_end (auto_solve_button); + // header_bar.pack_end (auto_solve_button); header_bar.pack_end (hint_button); set_titlebar (header_bar); @@ -401,9 +388,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { update_title (); }); - notify["readonly"].connect (() => { - save_game_button.sensitive = readonly; - }); + // notify["readonly"].connect (() => { + // save_game_button.sensitive = readonly; + // }); notify["can-go-back"].connect (() => { check_correct_button.sensitive = can_go_back && @@ -559,9 +546,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private void set_buttons_sensitive (bool sensitive) { generate_button.sensitive = controller.game_state != GameState.GENERATING; mode_switch.sensitive = sensitive; - load_game_button.sensitive = sensitive; - save_game_button.sensitive = sensitive; - save_game_as_button.sensitive = sensitive; + // load_game_button.sensitive = sensitive; + // save_game_button.sensitive = sensitive; + // save_game_as_button.sensitive = sensitive; restart_destructive = sensitive && !model.is_blank (controller.game_state); undo_button.sensitive = sensitive && can_go_back; redo_button.sensitive = sensitive && can_go_forward; @@ -571,7 +558,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { can_go_back; hint_button.sensitive = sensitive && controller.game_state == GameState.SOLVING; - auto_solve_button.sensitive = sensitive; + // auto_solve_button.sensitive = sensitive; } private void highlight_labels (Cell c, bool is_highlight) { From c1e3364a6738fe6e6d9c6a01f10e1712f7d2ed91 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 12:06:47 +0000 Subject: [PATCH 058/142] Ignore buildgtk4 directory --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8d2f44b..9aa38c1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ .flatpak *~ build - +buildgtk4 From 65dc91f32dce790c16502eaf33bc573238611b12 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 12:07:40 +0000 Subject: [PATCH 059/142] Simplify cell grid size control --- libcore/widgets/Cellgrid.vala | 79 +++++++++++++++-------------------- 1 file changed, 33 insertions(+), 46 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 002ede6..ab71a43 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -9,7 +9,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { public signal void stop_drawing (); public signal void leave (); - public View view { get; construct; } + public unowned View view { get; construct; } public Cell current_cell { get; set; } public Cell previous_cell { get; set; } public bool frozen { get; set; } @@ -49,8 +49,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { private int rows = 0; private int cols = 0; private bool dirty = false; /* Whether a redraw is needed */ - private double cell_width; /* Width of cell including frame */ - private double cell_height; /* Height of cell including frame */ + private double cell_size; /* Width and Height of cell */ private Gdk.RGBA grid_color; private Gdk.RGBA fill_color; @@ -75,6 +74,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } construct { + hexpand = true; + vexpand = true; _current_cell = NULL_CELL; colors = new Gdk.RGBA[2, 3]; grid_color.parse (Gnonograms.GRID_COLOR); @@ -105,8 +106,6 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { queue_draw (); }); - view.notify["cell-size"].connect (size_updated); - // view.controller.notify["dimensions"].connect (size_updated); view.controller.notify["game-state"].connect (() => { on_game_state_changed (); }); @@ -118,12 +117,15 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } }); - size_updated (); + view.notify["default-width"].connect (update_allocation); + view.notify["default-height"].connect (update_allocation); settings.changed["filled-color"].connect (set_colors); settings.changed["empty-color"].connect (set_colors); settings.changed["rows"].connect (size_updated); settings.changed["columns"].connect (size_updated); + + update_allocation (); } public void set_colors () { @@ -151,18 +153,24 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { cell_pattern_type = CellPatternType.UNDEFINED; /* Causes refresh of existing pattern */ } + private void update_allocation () { + var alloc = Gtk.Allocation () { + width = (int)((double)view.default_width * 0.66), + height = (int)((double)view.default_height * 0.66) + }; + content_width = alloc.width; + content_height = alloc.height; + size_updated (); + } private void size_updated () { Idle.add (() => { rows = (int)settings.get_uint ("rows"); cols = (int)settings.get_uint ("columns"); - // rows = (int)view.controller.dimensions.height; - // cols = (int)view.controller.dimensions.width; - cell_width = view.cell_size; - cell_height = view.cell_size; + var cell_width = content_width / cols; + var cell_height = content_height / rows; + cell_size = int.min (cell_width, cell_height); /* Cause refresh of existing pattern */ - highlight_pattern = new CellPattern.highlight (cell_width, cell_height); - content_width = cols * view.cell_size + (int)MINOR_GRID_LINE_WIDTH; - content_height = rows * view.cell_size + (int)MINOR_GRID_LINE_WIDTH; + highlight_pattern = new CellPattern.highlight (cell_size, cell_size); queue_draw (); return Source.REMOVE; }); @@ -197,8 +205,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { previous_pointer_y = y; } /* Calculate which cell the pointer is over */ - uint r = ((uint)((y) / cell_height)); - uint c = ((uint)(x / cell_width)); + uint r = ((uint)((y) / cell_size)); + uint c = ((uint)(x / cell_size)); if (r >= rows || c >= cols) { return; } @@ -220,13 +228,13 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { // Draw minor grid lines double y1 = MINOR_GRID_LINE_WIDTH; double x1 = MINOR_GRID_LINE_WIDTH; - double x2 = x1 + cols * view.cell_size; - double y2 = y1 + rows * view.cell_size; + double x2 = x1 + cols * cell_size; + double y2 = y1 + rows * cell_size; while (y1 < y2) { cr.move_to (x1, y1); cr.line_to (x2, y1); cr.stroke (); - y1 += view.cell_size; + y1 += cell_size; } y1 = MINOR_GRID_LINE_WIDTH; @@ -235,14 +243,14 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { cr.move_to (x1, y1); cr.line_to (x1, y2); cr.stroke (); - x1 += view.cell_size; + x1 += cell_size; } // Draw inner major grid lines cr.set_line_width (MAJOR_GRID_LINE_WIDTH); x1 = MINOR_GRID_LINE_WIDTH; while (y1 < y2) { - y1 += 5.0 * view.cell_size; + y1 += 5.0 * cell_size; cr.move_to (x1, y1); cr.line_to (x2, y1); cr.stroke (); @@ -250,7 +258,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { y1 = MINOR_GRID_LINE_WIDTH; while (x1 < x2) { - x1 += 5.0 * view.cell_size; + x1 += 5.0 * cell_size; cr.move_to (x1, y1); cr.line_to (x1, y2); cr.stroke (); @@ -279,8 +287,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { return; } - double x = cell.col * cell_width; - double y = cell.row * cell_height; + double x = cell.col * cell_size; + double y = cell.row * cell_size; CellPattern cell_pattern; switch (cell.state) { case CellState.EMPTY: @@ -299,7 +307,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { cr.save (); cell_pattern.move_to (x, y); /* Not needed for plain fill, but may use a pattern later */ cr.set_line_width (0.0); - cr.rectangle (x, y, cell_width, cell_height); + cr.rectangle (x, y, cell_size, cell_size); cr.set_source (cell_pattern.pattern); cr.fill (); cr.restore (); @@ -308,7 +316,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { cr.save (); /* Ensure highlight centred and slightly overlapping grid */ highlight_pattern.move_to (x, y); - cr.rectangle (x, y, cell_width, cell_width); + cr.rectangle (x, y, cell_size, cell_size); cr.clip (); cr.set_source (highlight_pattern.pattern); cr.set_operator (Cairo.Operator.OVER); @@ -324,27 +332,6 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { return; } - public override void measure ( - Gtk.Orientation orientation, - int for_size, - out int minimum, - out int natural, - out int minimum_baseline, - out int natural_baseline - ) { - if (orientation == HORIZONTAL) { - minimum = (int)(cell_width * cols); - natural = minimum; - minimum_baseline = -1; - natural_baseline = -1; - } else { - minimum = (int)(cell_height * rows); - natural = minimum; - minimum_baseline = -1; - natural_baseline = -1; - } - } - private class CellPattern { public Cairo.Pattern pattern; public double size { get; private set; } From 19b8d282f5829241b71d7678f2d7c8f819ffbf02 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 12:09:18 +0000 Subject: [PATCH 060/142] Replace cell-size with font-size in View; cleanup, simplify --- src/Controller.vala | 1 - src/View.vala | 60 +++++---------------------------------------- 2 files changed, 6 insertions(+), 55 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index f09e795..5dcf977 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -228,7 +228,6 @@ public class Gnonograms.Controller : GLib.Object { } private void restore_settings () { - view.cell_size = 48; current_game_path = ""; if (saved_state != null) { current_game_path = saved_state.get_string ("current-game-path"); diff --git a/src/View.vala b/src/View.vala index 8bc7909..d6235f8 100644 --- a/src/View.vala +++ b/src/View.vala @@ -17,34 +17,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private const string PAINT_EMPTY_ACCEL = "e"; // Must be lower case private const string PAINT_UNKNOWN_ACCEL = "x"; // Must be lower case -// public const string ACTION_GROUP = "win"; -// public const string ACTION_PREFIX = ACTION_GROUP + "."; -// public const string ACTION_UNDO = "action-undo"; -// public const string ACTION_REDO = "action-redo"; -// // public const string ACTION_ZOOM_IN = "action-zoom-in"; -// // public const string ACTION_ZOOM_OUT = "action-zoom-out"; -// public const string ACTION_CURSOR_UP = "action-cursor_up"; -// public const string ACTION_CURSOR_DOWN = "action-cursor_down"; -// public const string ACTION_CURSOR_LEFT = "action-cursor_left"; -// public const string ACTION_CURSOR_RIGHT = "action-cursor_right"; -// public const string ACTION_SETTING_MODE = "action-setting-mode"; -// public const string ACTION_SOLVING_MODE = "action-solving-mode"; -// public const string ACTION_GENERATING_MODE = "action-generating-mode"; -// public const string ACTION_OPEN = "action-open"; -// public const string ACTION_SAVE = "action-save"; -// public const string ACTION_SAVE_AS = "action-save-as"; -// public const string ACTION_PAINT_FILLED = "action-paint-filled"; -// public const string ACTION_PAINT_EMPTY = "action-paint-empty"; -// public const string ACTION_PAINT_UNKNOWN = "action-paint-unknown"; -// public const string ACTION_CHECK_ERRORS = "action-check-errors"; -// public const string ACTION_RESTART = "action-restart"; -// public const string ACTION_SOLVE = "action-solve"; -// public const string ACTION_HINT = "action-hint"; -// public const string ACTION_OPTIONS = "action-options"; -// #if WITH_DEBUGGING -// public const string ACTION_DEBUG_ROW = "action-debug-row"; -// public const string ACTION_DEBUG_COL = "action-debug-col"; -// #endif public static Gee.MultiMap action_accelerators; public static Gtk.Application app; private static GLib.ActionEntry [] view_action_entries = { @@ -80,7 +52,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public Cell previous_cell { get; set; } public Difficulty generator_grade { get; set; } public Difficulty game_grade { get; set; default = Difficulty.UNDEFINED;} - public int cell_size { get; set; default = 32; } + public double font_size { get; set; default = 10.0; } public string game_name { get { return controller.game_name; } } public bool strikeout_complete { get; set; } public bool readonly { get; set; default = false;} @@ -103,14 +75,11 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private Gtk.Label title_label; private Gtk.Label grade_label; private Gtk.Button generate_button; - // private Gtk.Button load_game_button; - // private Gtk.Button save_game_button; - // private Gtk.Button save_game_as_button; private Gtk.Button undo_button; private Gtk.Button redo_button; private Gtk.Button check_correct_button; private Gtk.Button hint_button; - private Gtk.Button auto_solve_button; + // private Gtk.Button auto_solve_button; private Gtk.Button restart_button; private uint drawing_with_key; @@ -281,18 +250,13 @@ public class Gnonograms.View : Gtk.ApplicationWindow { title_widget = progress_stack }; header_bar.add_css_class ("gnonograms-header"); - // header_bar.pack_start (load_game_button); - // header_bar.pack_start (save_game_button); - // header_bar.pack_start (save_game_as_button); header_bar.pack_start (generate_button); header_bar.pack_start (restart_button); header_bar.pack_start (undo_button); header_bar.pack_start (redo_button); header_bar.pack_start (check_correct_button); header_bar.pack_end (menu_button); - // header_bar.pack_end (generate_button); header_bar.pack_end (mode_switch); - // header_bar.pack_end (auto_solve_button); header_bar.pack_end (hint_button); set_titlebar (header_bar); @@ -308,26 +272,17 @@ public class Gnonograms.View : Gtk.ApplicationWindow { valign = START }; - var vert_sizegroup = new Gtk.SizeGroup (VERTICAL); - vert_sizegroup.add_widget (row_clue_box); - vert_sizegroup.add_widget (cell_grid); - var horiz_sizegroup = new Gtk.SizeGroup (HORIZONTAL); - horiz_sizegroup.add_widget (column_clue_box); - horiz_sizegroup.add_widget (cell_grid); - toast_overlay = new Adw.ToastOverlay () { valign = Gtk.Align.CENTER, - halign = Gtk.Align.CENTER + halign = Gtk.Align.CENTER, + hexpand = false, + vexpand = false }; main_grid = new Gtk.Grid () { focusable = true, // Needed for key controller to work row_spacing = 0, - column_spacing = GRID_COLUMN_SPACING, - valign = Gtk.Align.END, - halign = Gtk.Align.END, - hexpand = true, - vexpand = true + column_spacing = GRID_COLUMN_SPACING }; main_grid.attach (toast_overlay, 0, 0, 1, 1); /* show temporary messages */ main_grid.attach (row_clue_box, 0, 1, 1, 1); /* Clues for dimensions.height*/ @@ -546,9 +501,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private void set_buttons_sensitive (bool sensitive) { generate_button.sensitive = controller.game_state != GameState.GENERATING; mode_switch.sensitive = sensitive; - // load_game_button.sensitive = sensitive; - // save_game_button.sensitive = sensitive; - // save_game_as_button.sensitive = sensitive; restart_destructive = sensitive && !model.is_blank (controller.game_state); undo_button.sensitive = sensitive && can_go_back; redo_button.sensitive = sensitive && can_go_forward; From b5df550c285f559c47ad1a1713baacdac62a565e Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 12:10:54 +0000 Subject: [PATCH 061/142] Clue, ClueBox: get font size from View, simplify --- libcore/widgets/Clue.vala | 8 ++--- libcore/widgets/Cluebox.vala | 66 +++++++++++++++++++++--------------- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/libcore/widgets/Clue.vala b/libcore/widgets/Clue.vala index 42cfbe6..8a1f77b 100644 --- a/libcore/widgets/Clue.vala +++ b/libcore/widgets/Clue.vala @@ -25,9 +25,9 @@ class Gnonograms.Clue : Object { private Gee.List clue_blocks; // List of blocks based on clue - public Clue (bool _vertical_text, ClueBox cluebox) { + public Clue (Gtk.Orientation orientation, ClueBox cluebox) { Object ( - vertical_text: _vertical_text, + vertical_text: orientation == Gtk.Orientation.HORIZONTAL, cluebox: cluebox ); } @@ -190,12 +190,12 @@ class Gnonograms.Clue : Object { } private void update_markup () { - label.set_markup ("".printf (cluebox.font_size) + get_markup () + ""); + label.set_markup ("".printf (cluebox.font_size) + get_markup () + ""); update_tooltip (); } private void update_tooltip () { - label.set_tooltip_markup ("".printf (cluebox.font_size) + + label.set_tooltip_markup ("".printf (cluebox.font_size) + _("Freedom = %u").printf (cluebox.n_cells - Utils.blockextent_from_clue (_text)) + "" ); diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index 88bfe8e..d13b03e 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -6,51 +6,63 @@ */ public class Gnonograms.ClueBox : Gtk.Box { public unowned View view { get; construct; } - public int font_size { get; private set; } + public double font_size { get; set; } // The number of cells each clue addresses, monitored by clues public uint n_cells { get; set; default = 0; } private Gee.ArrayList clues; + private uint width { + get { + return view.controller.columns; + } + } + private uint height { + get { + return view.controller.rows; + } + } public ClueBox (Gtk.Orientation _orientation, View view) { Object ( view: view, - homogeneous: true, - spacing: 0, - hexpand: _orientation == Gtk.Orientation.HORIZONTAL ? false : true, - vexpand: _orientation == Gtk.Orientation.HORIZONTAL ? true : false, orientation: _orientation ); } construct { + homogeneous = true; + spacing = 0; + clues = new Gee.ArrayList (); - view.controller.notify ["dimensions"].connect (() => { - var new_n_clues = orientation == Gtk.Orientation.HORIZONTAL ? - view.controller.dimensions.width : - view.controller.dimensions.height; + view.controller.notify ["rows"].connect (on_dimensions_changed); + view.controller.notify ["columns"].connect (on_dimensions_changed); + view.bind_property ("font-size", this, "font-size"); - var new_n_cells = orientation == Gtk.Orientation.HORIZONTAL ? - view.controller.dimensions.height : - view.controller.dimensions.width; + on_dimensions_changed (); + } - foreach (var clue in clues) { - remove (clue.label); - } + private void on_dimensions_changed () { + if (width == 0 || height == 0) { + return; + } - clues.clear (); - n_cells = new_n_cells; - for (int index = 0; index < new_n_clues; index++) { - var clue = new Clue (orientation == Gtk.Orientation.HORIZONTAL, this); - clues.add (clue); - append (clue.label); - } - }); + var new_n_clues = orientation == Gtk.Orientation.HORIZONTAL ? + width : + height; - view.notify["cell-size"].connect (set_size); - } + var new_n_cells = orientation == Gtk.Orientation.HORIZONTAL ? + height : + width; + foreach (var clue in clues) { + remove (clue.label); + } - private void set_size () { - font_size = (int) ((double) view.cell_size * 0.525); + clues.clear (); + n_cells = new_n_cells; + for (int index = 0; index < new_n_clues; index++) { + var clue = new Clue (orientation, this); + clues.add (clue); + append (clue.label); + } } public string[] get_clue_texts () { From acda4e0bb430c0349a17484d1873956b6a1b56d4 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 13:55:22 +0000 Subject: [PATCH 062/142] COntinue to rework grid drawing --- libcore/widgets/Cellgrid.vala | 50 +++++++++++++++++------------------ libcore/widgets/Clue.vala | 11 +++++--- libcore/widgets/Cluebox.vala | 4 +-- src/View.vala | 15 +++++------ 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index ab71a43..4cdf620 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -46,10 +46,10 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { private const double MINOR_GRID_LINE_WIDTH = 1.0; private Gdk.RGBA[, ] colors; - private int rows = 0; - private int cols = 0; + private uint rows = 5; + private uint cols = 5; private bool dirty = false; /* Whether a redraw is needed */ - private double cell_size; /* Width and Height of cell */ + private double cell_size = 6.0; /* Width and Height of cell */ private Gdk.RGBA grid_color; private Gdk.RGBA fill_color; @@ -117,15 +117,12 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } }); - view.notify["default-width"].connect (update_allocation); - view.notify["default-height"].connect (update_allocation); - settings.changed["filled-color"].connect (set_colors); settings.changed["empty-color"].connect (set_colors); - settings.changed["rows"].connect (size_updated); - settings.changed["columns"].connect (size_updated); + view.controller.notify["rows"].connect (size_updated); + view.controller.notify["columns"].connect (size_updated); - update_allocation (); + size_updated (); } public void set_colors () { @@ -153,27 +150,30 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { cell_pattern_type = CellPatternType.UNDEFINED; /* Causes refresh of existing pattern */ } - private void update_allocation () { - var alloc = Gtk.Allocation () { - width = (int)((double)view.default_width * 0.66), - height = (int)((double)view.default_height * 0.66) - }; - content_width = alloc.width; - content_height = alloc.height; - size_updated (); + public override void size_allocate (int w, int h, int bl) { + if (cols > 0 && rows > 0) { + content_width = w; + content_height = h; + size_updated (); + } + + warning ("content %i, %i", content_width, content_height); + base.size_allocate (w, h, bl); } + private void size_updated () { - Idle.add (() => { - rows = (int)settings.get_uint ("rows"); - cols = (int)settings.get_uint ("columns"); - var cell_width = content_width / cols; - var cell_height = content_height / rows; - cell_size = int.min (cell_width, cell_height); + rows = view.controller.rows; + cols = view.controller.columns; + warning ("SIZE UPDATED %u, %u", rows, cols); + if (rows > 0 && cols > 0) { + var cell_width = (double) (content_width / cols); + var cell_height = (double) (content_height / rows); + cell_size = double.min (cell_width, cell_height); + warning ("cell size %f", cell_size); /* Cause refresh of existing pattern */ highlight_pattern = new CellPattern.highlight (cell_size, cell_size); queue_draw (); - return Source.REMOVE; - }); + } } private void draw_func (Gtk.DrawingArea drawing_area, Cairo.Context cr, int x, int y) { diff --git a/libcore/widgets/Clue.vala b/libcore/widgets/Clue.vala index 8a1f77b..6a3c6ae 100644 --- a/libcore/widgets/Clue.vala +++ b/libcore/widgets/Clue.vala @@ -37,14 +37,17 @@ class Gnonograms.Clue : Object { xalign = _vertical_text ? (float)0.5 : (float)1.0, yalign = vertical_text ? (float)1.0 : (float)0.5, has_tooltip = true, - use_markup = true + use_markup = true, + hexpand = false, + vexpand = false }; text = "0"; label.realize.connect_after (update_markup); cluebox.notify["n_cells"].connect (update_tooltip); - cluebox.notify["font-size"].connect (update_markup); + cluebox.view.notify["font-size"].connect (() => { + warning ("font size notify"); update_markup ();}); } public void highlight (bool is_highlight) { @@ -190,12 +193,12 @@ class Gnonograms.Clue : Object { } private void update_markup () { - label.set_markup ("".printf (cluebox.font_size) + get_markup () + ""); + label.set_markup ("".printf (cluebox.view.font_size) + get_markup () + ""); update_tooltip (); } private void update_tooltip () { - label.set_tooltip_markup ("".printf (cluebox.font_size) + + label.set_tooltip_markup ("".printf (cluebox.view.font_size) + _("Freedom = %u").printf (cluebox.n_cells - Utils.blockextent_from_clue (_text)) + "" ); diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index d13b03e..577e290 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -6,7 +6,7 @@ */ public class Gnonograms.ClueBox : Gtk.Box { public unowned View view { get; construct; } - public double font_size { get; set; } + // public double font_size { get; set; } // The number of cells each clue addresses, monitored by clues public uint n_cells { get; set; default = 0; } private Gee.ArrayList clues; @@ -35,11 +35,11 @@ public class Gnonograms.ClueBox : Gtk.Box { clues = new Gee.ArrayList (); view.controller.notify ["rows"].connect (on_dimensions_changed); view.controller.notify ["columns"].connect (on_dimensions_changed); - view.bind_property ("font-size", this, "font-size"); on_dimensions_changed (); } + private void on_dimensions_changed () { if (width == 0 || height == 0) { return; diff --git a/src/View.vala b/src/View.vala index d6235f8..f1b8289 100644 --- a/src/View.vala +++ b/src/View.vala @@ -262,27 +262,24 @@ public class Gnonograms.View : Gtk.ApplicationWindow { set_titlebar (header_bar); row_clue_box = new ClueBox (Gtk.Orientation.VERTICAL, this) { - halign = Gtk.Align.END, + halign = Gtk.Align.END }; column_clue_box = new ClueBox (Gtk.Orientation.HORIZONTAL, this) { - valign = Gtk.Align.END, - }; - cell_grid = new CellGrid (this) { - halign = START, - valign = START + valign = Gtk.Align.END }; + cell_grid = new CellGrid (this); toast_overlay = new Adw.ToastOverlay () { valign = Gtk.Align.CENTER, halign = Gtk.Align.CENTER, - hexpand = false, - vexpand = false }; main_grid = new Gtk.Grid () { focusable = true, // Needed for key controller to work row_spacing = 0, - column_spacing = GRID_COLUMN_SPACING + column_spacing = GRID_COLUMN_SPACING, + hexpand = false, + vexpand = false }; main_grid.attach (toast_overlay, 0, 0, 1, 1); /* show temporary messages */ main_grid.attach (row_clue_box, 0, 1, 1, 1); /* Clues for dimensions.height*/ From 2a83cc892f3ab49fb2f879053255e198a8b30435 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 15:21:16 +0000 Subject: [PATCH 063/142] Lose unused --- src/View.vala | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/View.vala b/src/View.vala index f1b8289..2df006f 100644 --- a/src/View.vala +++ b/src/View.vala @@ -6,12 +6,6 @@ */ public class Gnonograms.View : Gtk.ApplicationWindow { - private const double USABLE_MONITOR_HEIGHT = 0.85; - private const double USABLE_MONITOR_WIDTH = 0.95; - private const int GRID_BORDER = 6; - private const int GRID_COLUMN_SPACING = 6; - private const double TYPICAL_MAX_BLOCKS_RATIO = 0.3; - private const int WINDOW_INCREMENT = 8; private const uint PROGRESS_DELAY_MSEC = 500; private const string PAINT_FILL_ACCEL = "f"; // Must be lower case private const string PAINT_EMPTY_ACCEL = "e"; // Must be lower case @@ -277,7 +271,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { main_grid = new Gtk.Grid () { focusable = true, // Needed for key controller to work row_spacing = 0, - column_spacing = GRID_COLUMN_SPACING, + column_spacing = 6, hexpand = false, vexpand = false }; From 86f2493e4fb0e58d8fb605c2c5d6fe996adf8027 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 17:00:57 +0000 Subject: [PATCH 064/142] CellGrid: Allow non-square cells --- libcore/widgets/Cellgrid.vala | 92 +++++++++++++++++------------------ 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 4cdf620..8633f22 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -14,7 +14,6 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { public Cell previous_cell { get; set; } public bool frozen { get; set; } public bool draw_only { get; set; default = false;} - /* Could have more options for cell pattern*/ private CellPatternType _cell_pattern_type; public CellPatternType cell_pattern_type { @@ -46,10 +45,10 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { private const double MINOR_GRID_LINE_WIDTH = 1.0; private Gdk.RGBA[, ] colors; - private uint rows = 5; - private uint cols = 5; + private double cell_width; /* Width and Height of cell including frame */ + private double cell_height; /* Width and Height of cell including frame */ private bool dirty = false; /* Whether a redraw is needed */ - private double cell_size = 6.0; /* Width and Height of cell */ + private Gdk.RGBA grid_color; private Gdk.RGBA fill_color; @@ -119,10 +118,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { settings.changed["filled-color"].connect (set_colors); settings.changed["empty-color"].connect (set_colors); - view.controller.notify["rows"].connect (size_updated); - view.controller.notify["columns"].connect (size_updated); - - size_updated (); + view.controller.notify["rows"].connect (queue_allocate); + view.controller.notify["columns"].connect (queue_allocate); } public void set_colors () { @@ -151,29 +148,28 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } public override void size_allocate (int w, int h, int bl) { - if (cols > 0 && rows > 0) { + if (view.controller.rows > 0 && view.controller.columns > 0) { content_width = w; content_height = h; - size_updated (); + cell_width = (double) w / (double) view.controller.columns; + cell_height = (double) h / (double) view.controller.rows; + /* Cause refresh of existing pattern */ + highlight_pattern = new CellPattern.highlight (cell_width, cell_height); } - warning ("content %i, %i", content_width, content_height); base.size_allocate (w, h, bl); } - private void size_updated () { - rows = view.controller.rows; - cols = view.controller.columns; - warning ("SIZE UPDATED %u, %u", rows, cols); - if (rows > 0 && cols > 0) { - var cell_width = (double) (content_width / cols); - var cell_height = (double) (content_height / rows); - cell_size = double.min (cell_width, cell_height); - warning ("cell size %f", cell_size); - /* Cause refresh of existing pattern */ - highlight_pattern = new CellPattern.highlight (cell_size, cell_size); - queue_draw (); + public override void measure (Gtk.Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) { + if (orientation == Gtk.Orientation.HORIZONTAL) { + natural = content_width; + } else { + natural = content_height; } + // Allow to shrink + minimum = 0; + minimum_baseline = 0; + natural_baseline = 0; } private void draw_func (Gtk.DrawingArea drawing_area, Cairo.Context cr, int x, int y) { @@ -205,11 +201,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { previous_pointer_y = y; } /* Calculate which cell the pointer is over */ - uint r = ((uint)((y) / cell_size)); - uint c = ((uint)(x / cell_size)); - if (r >= rows || c >= cols) { - return; - } + uint r = ((uint)((y) / cell_height)); + uint c = ((uint)(x / cell_width)); /* Construct cell beneath pointer */ Cell cell = {r, c, array.get_data_from_rc (r, c)}; if (!cell.equal (current_cell)) { @@ -228,13 +221,13 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { // Draw minor grid lines double y1 = MINOR_GRID_LINE_WIDTH; double x1 = MINOR_GRID_LINE_WIDTH; - double x2 = x1 + cols * cell_size; - double y2 = y1 + rows * cell_size; + double x2 = x1 + view.controller.columns * cell_width; + double y2 = y1 + view.controller.rows * cell_height; while (y1 < y2) { cr.move_to (x1, y1); cr.line_to (x2, y1); cr.stroke (); - y1 += cell_size; + y1 += cell_height; } y1 = MINOR_GRID_LINE_WIDTH; @@ -243,14 +236,14 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { cr.move_to (x1, y1); cr.line_to (x1, y2); cr.stroke (); - x1 += cell_size; + x1 += cell_width; } // Draw inner major grid lines cr.set_line_width (MAJOR_GRID_LINE_WIDTH); x1 = MINOR_GRID_LINE_WIDTH; while (y1 < y2) { - y1 += 5.0 * cell_size; + y1 += 5.0 * cell_height; cr.move_to (x1, y1); cr.line_to (x2, y1); cr.stroke (); @@ -258,7 +251,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { y1 = MINOR_GRID_LINE_WIDTH; while (x1 < x2) { - x1 += 5.0 * cell_size; + x1 += 5.0 * cell_width; cr.move_to (x1, y1); cr.line_to (x1, y2); cr.stroke (); @@ -287,8 +280,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { return; } - double x = cell.col * cell_size; - double y = cell.row * cell_size; + double x = cell.col * cell_width; + double y = cell.row * cell_height; CellPattern cell_pattern; switch (cell.state) { case CellState.EMPTY: @@ -307,7 +300,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { cr.save (); cell_pattern.move_to (x, y); /* Not needed for plain fill, but may use a pattern later */ cr.set_line_width (0.0); - cr.rectangle (x, y, cell_size, cell_size); + cr.rectangle (x, y, cell_width, cell_height); cr.set_source (cell_pattern.pattern); cr.fill (); cr.restore (); @@ -316,7 +309,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { cr.save (); /* Ensure highlight centred and slightly overlapping grid */ highlight_pattern.move_to (x, y); - cr.rectangle (x, y, cell_size, cell_size); + cr.rectangle (x, y, cell_width, cell_height); cr.clip (); cr.set_source (highlight_pattern.pattern); cr.set_operator (Cairo.Operator.OVER); @@ -332,9 +325,11 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { return; } - private class CellPattern { + private class CellPattern : GLib.Object { public Cairo.Pattern pattern; - public double size { get; private set; } + // public double size { get; private set; } + public double width { get; construct; } + public double height { get; construct; } private double red; private double green; private double blue; @@ -357,15 +352,20 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } public CellPattern.highlight (double wd, double ht) { - var r = (wd + ht) / 4.0; - size = 2 * r; + Object ( + width: wd, + height: ht + ); + } - Cairo.ImageSurface surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, (int)size, (int)size); - Cairo.Context context = new Cairo.Context (surface); + construct { + var r = double.min (width, height) / 2.0; + var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, (int)width, (int)height); + var context = new Cairo.Context (surface); context.set_source_rgb (0.0, 0.0, 0.0); - context.rectangle (0, 0, size, size); + context.rectangle (0, 0, width, height); context.fill (); - context.arc (r, r, r - 2.0, 0, 2 * Math.PI); + context.arc (width / 2.0, height / 2.0, r - 2.0, 0, 2 * Math.PI); context.set_source_rgba (1.0, 1.0, 1.0, 0.5); context.set_operator (Cairo.Operator.SOURCE); context.fill (); From 97de9d4646ce975e98040257cec3cf29f7145666 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 17:01:10 +0000 Subject: [PATCH 065/142] Cleanup --- libcore/widgets/Cluebox.vala | 15 +++++---------- src/View.vala | 22 ++++++---------------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index 577e290..3fa9c82 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -10,11 +10,6 @@ public class Gnonograms.ClueBox : Gtk.Box { // The number of cells each clue addresses, monitored by clues public uint n_cells { get; set; default = 0; } private Gee.ArrayList clues; - private uint width { - get { - return view.controller.columns; - } - } private uint height { get { return view.controller.rows; @@ -41,17 +36,17 @@ public class Gnonograms.ClueBox : Gtk.Box { private void on_dimensions_changed () { - if (width == 0 || height == 0) { + if (view.controller.columns == 0 || view.controller.rows == 0) { return; } var new_n_clues = orientation == Gtk.Orientation.HORIZONTAL ? - width : - height; + view.controller.columns : + view.controller.rows; var new_n_cells = orientation == Gtk.Orientation.HORIZONTAL ? - height : - width; + view.controller.rows : + view.controller.columns; foreach (var clue in clues) { remove (clue.label); } diff --git a/src/View.vala b/src/View.vala index 2df006f..d807264 100644 --- a/src/View.vala +++ b/src/View.vala @@ -46,7 +46,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public Cell previous_cell { get; set; } public Difficulty generator_grade { get; set; } public Difficulty game_grade { get; set; default = Difficulty.UNDEFINED;} - public double font_size { get; set; default = 10.0; } + public double font_size { get; set; default = 12.0; } public string game_name { get { return controller.game_name; } } public bool strikeout_complete { get; set; } public bool readonly { get; set; default = false;} @@ -80,8 +80,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public View (Model _model, Controller controller) { Object ( model: _model, - controller: controller, - title: _("Gnonograms") + controller: controller ); } @@ -131,6 +130,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } construct { + title = _("Gnonograms"); set_default_size (900, 700); var granite_settings = Granite.Settings.get_default (); var gtk_settings = Gtk.Settings.get_default (); @@ -152,7 +152,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { app.set_accels_for_action (ACTION_PREFIX + action, accels_array); } - undo_button = new HeaderButton ( "edit-undo-symbolic", ACTION_PREFIX + ACTION_UNDO, @@ -192,7 +191,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { _("Generate New Puzzle") ); - var app_popover = new AppPopover (controller); menu_button = new Gtk.MenuButton () { @@ -255,12 +253,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { set_titlebar (header_bar); - row_clue_box = new ClueBox (Gtk.Orientation.VERTICAL, this) { - halign = Gtk.Align.END - }; - column_clue_box = new ClueBox (Gtk.Orientation.HORIZONTAL, this) { - valign = Gtk.Align.END - }; + row_clue_box = new ClueBox (Gtk.Orientation.VERTICAL, this); + column_clue_box = new ClueBox (Gtk.Orientation.HORIZONTAL, this); cell_grid = new CellGrid (this); toast_overlay = new Adw.ToastOverlay () { @@ -269,11 +263,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { }; main_grid = new Gtk.Grid () { - focusable = true, // Needed for key controller to work - row_spacing = 0, - column_spacing = 6, - hexpand = false, - vexpand = false + focusable = true // Needed for key controller to work }; main_grid.attach (toast_overlay, 0, 0, 1, 1); /* show temporary messages */ main_grid.attach (row_clue_box, 0, 1, 1, 1); /* Clues for dimensions.height*/ From 3c1929ee2fb154faa37b8f2ac8fe5ddc4fcafe07 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 17:05:27 +0000 Subject: [PATCH 066/142] CellGrid: Do not set baseline --- libcore/widgets/Cellgrid.vala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 8633f22..1dd308d 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -168,8 +168,9 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } // Allow to shrink minimum = 0; - minimum_baseline = 0; - natural_baseline = 0; + // Must not set baseline on non-text widget + minimum_baseline = -1; + natural_baseline = -1; } private void draw_func (Gtk.DrawingArea drawing_area, Cairo.Context cr, int x, int y) { From 7883e3aab887c63de79ed63bfb5ab42d63186ab5 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 17:17:46 +0000 Subject: [PATCH 067/142] Bind rows and columns to settings --- src/Controller.vala | 36 ++++++++---------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index 5dcf977..aff10c6 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -58,6 +58,7 @@ public class Gnonograms.Controller : GLib.Object { solver = new Solver (dimensions); game_name = _(UNTITLED_NAME); }); + notify["rows"].connect (() => { solver = new Solver (dimensions); game_name = _(UNTITLED_NAME); @@ -91,13 +92,12 @@ public class Gnonograms.Controller : GLib.Object { Gnonograms.UNSAVED_FILENAME ); - if (saved_state != null && settings != null) { - saved_state.bind ("mode", this, "game-state", SettingsBindFlags.DEFAULT); - settings.bind ("grade", this, "generator-grade", SettingsBindFlags.DEFAULT); - settings.bind ("clue-help", view, "strikeout-complete", SettingsBindFlags.DEFAULT); - } else { - restore_settings (); - } + saved_state.bind ("mode", this, "game-state", SettingsBindFlags.DEFAULT); + saved_state.bind ("current-game-path", this, "current-game-path", SettingsBindFlags.DEFAULT); + settings.bind ("grade", this, "generator-grade", SettingsBindFlags.DEFAULT); + settings.bind ("clue-help", view, "strikeout-complete", SettingsBindFlags.DEFAULT); + settings.bind ("rows", this, "rows", SettingsBindFlags.DEFAULT); + settings.bind ("columns", this, "columns", SettingsBindFlags.DEFAULT); view.present (); /* @@ -133,7 +133,7 @@ public class Gnonograms.Controller : GLib.Object { if (!restore_game.end (res)) { /* Error normally thrown if running without installing */ warning ("Restoring game failed"); - restore_dimensions (); + // restore_dimensions (); new_game (); } }); @@ -227,24 +227,6 @@ public class Gnonograms.Controller : GLib.Object { } } - private void restore_settings () { - current_game_path = ""; - if (saved_state != null) { - current_game_path = saved_state.get_string ("current-game-path"); - } - - restore_dimensions (); - } - - private void restore_dimensions () { - columns = 15; - rows = 10; - if (settings != null) { - columns = settings.get_uint ("columns").clamp (10, 50); - rows = settings.get_uint ("rows").clamp (10, 50); - } - } - private async bool restore_game () { if (temporary_game_path != null) { var current_game_file = File.new_for_path (temporary_game_path); @@ -306,8 +288,6 @@ public class Gnonograms.Controller : GLib.Object { load_game_async.begin (game, (obj, res) => { if (!load_game_async.end (res)) { warning ("Load game failed"); - current_game_path = ""; - restore_dimensions (); new_or_random_game (); } }); From 9047f98a32e31392c9406a5d8eccb22f42ba659f Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 18:01:01 +0000 Subject: [PATCH 068/142] Add and use font scaling setting --- ...com.github.jeremypw.gnonograms.gschema.xml | 8 +++++++ libcore/widgets/Clue.vala | 7 +++--- libcore/widgets/Cluebox.vala | 1 - src/Controller.vala | 2 +- src/HeaderBar/AppPopover.vala | 23 +++++++++++++++++-- src/View.vala | 4 +++- 6 files changed, 36 insertions(+), 9 deletions(-) diff --git a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml index 3e9347a..b200241 100644 --- a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml +++ b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml @@ -70,6 +70,14 @@ Defaults to yellow + + + 100 + Scaling of label text + + The size of the clue label text relative to system text. + + diff --git a/libcore/widgets/Clue.vala b/libcore/widgets/Clue.vala index 6a3c6ae..f26ba5f 100644 --- a/libcore/widgets/Clue.vala +++ b/libcore/widgets/Clue.vala @@ -46,8 +46,7 @@ class Gnonograms.Clue : Object { label.realize.connect_after (update_markup); cluebox.notify["n_cells"].connect (update_tooltip); - cluebox.view.notify["font-size"].connect (() => { - warning ("font size notify"); update_markup ();}); + cluebox.view.notify["font-scaling"].connect (update_markup); } public void highlight (bool is_highlight) { @@ -193,12 +192,12 @@ class Gnonograms.Clue : Object { } private void update_markup () { - label.set_markup ("".printf (cluebox.view.font_size) + get_markup () + ""); + label.set_markup ("".printf (cluebox.view.font_scaling) + get_markup () + ""); update_tooltip (); } private void update_tooltip () { - label.set_tooltip_markup ("".printf (cluebox.view.font_size) + + label.set_tooltip_markup ("".printf (cluebox.view.font_scaling) + _("Freedom = %u").printf (cluebox.n_cells - Utils.blockextent_from_clue (_text)) + "" ); diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index 3fa9c82..9e69afd 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -6,7 +6,6 @@ */ public class Gnonograms.ClueBox : Gtk.Box { public unowned View view { get; construct; } - // public double font_size { get; set; } // The number of cells each clue addresses, monitored by clues public uint n_cells { get; set; default = 0; } private Gee.ArrayList clues; diff --git a/src/Controller.vala b/src/Controller.vala index aff10c6..3c753f8 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -30,7 +30,7 @@ public class Gnonograms.Controller : GLib.Object { private Solver? solver; private SimpleRandomGameGenerator? generator; private Gnonograms.History history; - public string current_game_path { get; private set; default = ""; } + public string current_game_path { get; set; default = ""; } private string saved_games_folder; private string? temporary_game_path = null; diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 53daae9..fb29fdd 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -18,7 +18,8 @@ public class Gnonograms.AppPopover : Gtk.Popover { _("Zoom Out") ); zoom_out_button.clicked.connect (() => { - controller.decrease_fontsize (); + var current_font_scale = settings.get_int ("font-scaling"); + settings.set_int ("font-scaling", current_font_scale - 10); }); var zoom_in_button = new Gtk.Button.from_icon_name ("zoom-in-symbolic"); @@ -27,7 +28,24 @@ public class Gnonograms.AppPopover : Gtk.Popover { _("Zoom In") ); zoom_in_button.clicked.connect (() => { - controller.increase_fontsize (); + var current_font_scale = settings.get_int ("font-scaling"); + settings.set_int ("font-scaling", current_font_scale + 10); + }); + + var zoom_default_button = new Gtk.Button () { + label = settings.get_int ("font-scaling").to_string () + "%" + }; + + zoom_default_button.tooltip_markup = Granite.markup_accel_tooltip ( + {"0"}, + _("Zoom Default") + ); + zoom_default_button.clicked.connect (() => { + var current_font_scale = settings.get_int ("font-scaling"); + settings.set_int ("font-scaling", 100); + }); + settings.changed["font-scaling"].connect (() => { + zoom_default_button.label = settings.get_int ("font-scaling").to_string () + "%"; }); var font_size_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) { @@ -39,6 +57,7 @@ public class Gnonograms.AppPopover : Gtk.Popover { }; font_size_box.add_css_class (Granite.STYLE_CLASS_LINKED); font_size_box.append (zoom_out_button); + font_size_box.append (zoom_default_button); font_size_box.append (zoom_in_button); var title_entry = new Gtk.Entry () { diff --git a/src/View.vala b/src/View.vala index d807264..067c64a 100644 --- a/src/View.vala +++ b/src/View.vala @@ -46,7 +46,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public Cell previous_cell { get; set; } public Difficulty generator_grade { get; set; } public Difficulty game_grade { get; set; default = Difficulty.UNDEFINED;} - public double font_size { get; set; default = 12.0; } + public int font_scaling { get; set; default = 100; } // Percentage font scaling for clue labels public string game_name { get { return controller.game_name; } } public bool strikeout_complete { get; set; } public bool readonly { get; set; default = false;} @@ -141,6 +141,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { gtk_settings.gtk_application_prefer_dark_theme = prefer_dark; }); + settings.bind ("font-scaling", this, "font-scaling", SettingsBindFlags.DEFAULT); + var view_actions = new GLib.SimpleActionGroup (); view_actions.add_action_entries (view_action_entries, this); insert_action_group (ACTION_GROUP, view_actions); From 219ad9f74ca62014836266568c9d542d62e08d84 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 19:14:19 +0000 Subject: [PATCH 069/142] Set main grid margins --- src/View.vala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/View.vala b/src/View.vala index 067c64a..d04cf5f 100644 --- a/src/View.vala +++ b/src/View.vala @@ -265,8 +265,15 @@ public class Gnonograms.View : Gtk.ApplicationWindow { }; main_grid = new Gtk.Grid () { - focusable = true // Needed for key controller to work + focusable = true, // Needed for key controller to work + row_spacing = 6, + column_spacing = 6, + margin_start = 6, + margin_end = 6, + margin_top = 6, + margin_bottom = 6 }; + main_grid.attach (toast_overlay, 0, 0, 1, 1); /* show temporary messages */ main_grid.attach (row_clue_box, 0, 1, 1, 1); /* Clues for dimensions.height*/ main_grid.attach (column_clue_box, 1, 0, 1, 1); /* Clues for columns */ From a56d0f67c3f5a75d46dbfaae2aa475996cc2dd8a Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 19:14:35 +0000 Subject: [PATCH 070/142] Cleanup --- libcore/widgets/Clue.vala | 4 +--- libcore/widgets/Cluebox.vala | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/libcore/widgets/Clue.vala b/libcore/widgets/Clue.vala index f26ba5f..158b2f3 100644 --- a/libcore/widgets/Clue.vala +++ b/libcore/widgets/Clue.vala @@ -37,9 +37,7 @@ class Gnonograms.Clue : Object { xalign = _vertical_text ? (float)0.5 : (float)1.0, yalign = vertical_text ? (float)1.0 : (float)0.5, has_tooltip = true, - use_markup = true, - hexpand = false, - vexpand = false + use_markup = true }; text = "0"; diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index 9e69afd..070cffe 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -24,7 +24,6 @@ public class Gnonograms.ClueBox : Gtk.Box { construct { homogeneous = true; - spacing = 0; clues = new Gee.ArrayList (); view.controller.notify ["rows"].connect (on_dimensions_changed); From eebd33874aa6f811978f611818d2e07dc6a1733c Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 19:16:02 +0000 Subject: [PATCH 071/142] Tweak drawing grid lines --- libcore/widgets/Cellgrid.vala | 37 +++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 1dd308d..5856495 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -220,10 +220,10 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { cr.set_line_width (MINOR_GRID_LINE_WIDTH); // Draw minor grid lines - double y1 = MINOR_GRID_LINE_WIDTH; - double x1 = MINOR_GRID_LINE_WIDTH; - double x2 = x1 + view.controller.columns * cell_width; - double y2 = y1 + view.controller.rows * cell_height; + double y1 = 0; + double x1 = 0; + double x2 = content_width - 1; + double y2 = content_height - 1; while (y1 < y2) { cr.move_to (x1, y1); cr.line_to (x2, y1); @@ -231,8 +231,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { y1 += cell_height; } - y1 = MINOR_GRID_LINE_WIDTH; - // x1 = MINOR_GRID_LINE_WIDTH; + y1 = 0; + while (x1 < x2) { cr.move_to (x1, y1); cr.line_to (x1, y2); @@ -240,38 +240,46 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { x1 += cell_width; } + x1 = 0; + // Draw inner major grid lines cr.set_line_width (MAJOR_GRID_LINE_WIDTH); - x1 = MINOR_GRID_LINE_WIDTH; + var increment = 5.0 * cell_height; + y1 = increment; while (y1 < y2) { - y1 += 5.0 * cell_height; cr.move_to (x1, y1); cr.line_to (x2, y1); cr.stroke (); + y1 += increment; } y1 = MINOR_GRID_LINE_WIDTH; + increment = 5.0 * cell_width; + x1 = increment; while (x1 < x2) { - x1 += 5.0 * cell_width; cr.move_to (x1, y1); cr.line_to (x1, y2); cr.stroke (); + x1 += increment; } // Draw frame cr.set_line_width (MINOR_GRID_LINE_WIDTH); - y1 = 0; - x1 = 0; + y1 = MINOR_GRID_LINE_WIDTH; + x1 = MINOR_GRID_LINE_WIDTH; cr.move_to (x1, y1); - cr.line_to (x2, y1); + cr.line_to (x2 - x1, y1); cr.stroke (); - cr.line_to (x2, y2); + cr.move_to (x2 - x1, y1); + cr.line_to (x2 - x1, y2 - y1); cr.stroke (); - cr.line_to (x1, y2); + cr.move_to (x2 - x1, y2 - y1); + cr.line_to (x1, y2 - y1); cr.stroke (); + cr.move_to (x1, y2 - y1); cr.line_to (x1, y1); cr.stroke (); } @@ -328,7 +336,6 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { private class CellPattern : GLib.Object { public Cairo.Pattern pattern; - // public double size { get; private set; } public double width { get; construct; } public double height { get; construct; } private double red; From f340c7a0991e2a495c4f9aecf73da4706675508d Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 19:30:40 +0000 Subject: [PATCH 072/142] Main grid as child of ToastOverlay --- src/View.vala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/View.vala b/src/View.vala index d04cf5f..74a8828 100644 --- a/src/View.vala +++ b/src/View.vala @@ -259,10 +259,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { column_clue_box = new ClueBox (Gtk.Orientation.HORIZONTAL, this); cell_grid = new CellGrid (this); - toast_overlay = new Adw.ToastOverlay () { - valign = Gtk.Align.CENTER, - halign = Gtk.Align.CENTER, - }; + main_grid = new Gtk.Grid () { focusable = true, // Needed for key controller to work @@ -274,7 +271,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { margin_bottom = 6 }; - main_grid.attach (toast_overlay, 0, 0, 1, 1); /* show temporary messages */ main_grid.attach (row_clue_box, 0, 1, 1, 1); /* Clues for dimensions.height*/ main_grid.attach (column_clue_box, 1, 0, 1, 1); /* Clues for columns */ main_grid.attach (cell_grid, 1, 1, 1, 1); @@ -290,7 +286,11 @@ public class Gnonograms.View : Gtk.ApplicationWindow { return; }); - child = main_grid; + toast_overlay = new Adw.ToastOverlay () { + child = main_grid + }; + + child = toast_overlay; var flags = BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE; bind_property ( From 5591048738728b50aab3c9c86a253d5c6af7c525 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 19:59:22 +0000 Subject: [PATCH 073/142] Lose TRIVIAL and VERY_EASY grades --- ...com.github.jeremypw.gnonograms.gschema.xml | 14 ++++++------- libcore/Enums.vala | 20 +++++++------------ libcore/Solver.vala | 2 -- src/services/RandomPatternGenerator.vala | 2 -- 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml index b200241..8edc557 100644 --- a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml +++ b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml @@ -1,14 +1,12 @@ - - - - - - - - + + + + + + diff --git a/libcore/Enums.vala b/libcore/Enums.vala index c970790..6e7d10e 100644 --- a/libcore/Enums.vala +++ b/libcore/Enums.vala @@ -6,23 +6,17 @@ */ namespace Gnonograms { public enum Difficulty { - TRIVIAL = 0, - VERY_EASY = 1, - EASY = 2, - MODERATE = 3, - HARD = 4 , - CHALLENGING = 5, - ADVANCED = 6, - MAXIMUM = 7, /* Max grade for generated puzzles (possibly ambiguous)*/ - COMPUTER = 8, /* Grade for requested computer solving */ + EASY = 0, + MODERATE = 1, + HARD = 2 , + CHALLENGING = 3, + ADVANCED = 4, + MAXIMUM = 5, /* Max grade for generated puzzles (possibly ambiguous)*/ + COMPUTER = 9, /* Grade for requested computer solving */ UNDEFINED = 99; public string to_string () { switch (this) { - case Difficulty.TRIVIAL: - return _("Trivial"); - case Difficulty.VERY_EASY: - return _("Very Easy"); case Difficulty.EASY: return _("Easy"); case Difficulty.MODERATE: diff --git a/libcore/Solver.vala b/libcore/Solver.vala index 6c808ce..ad47252 100644 --- a/libcore/Solver.vala +++ b/libcore/Solver.vala @@ -162,8 +162,6 @@ advanced_only = false; human_only = true; switch (grade) { - case Difficulty.TRIVIAL: - case Difficulty.VERY_EASY: case Difficulty.EASY: case Difficulty.MODERATE: case Difficulty.HARD: diff --git a/src/services/RandomPatternGenerator.vala b/src/services/RandomPatternGenerator.vala index dc5df0c..45471c2 100644 --- a/src/services/RandomPatternGenerator.vala +++ b/src/services/RandomPatternGenerator.vala @@ -66,8 +66,6 @@ public class Gnonograms.RandomPatternGenerator : Object { edge_bias = 0; switch (grade) { - case Difficulty.TRIVIAL: - case Difficulty.VERY_EASY: case Difficulty.EASY: threshold = 60; min_freedom = 1; From 57f00e47c6d8722629e7184b05d6370f6cf9402e Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 19:59:46 +0000 Subject: [PATCH 074/142] Connect grade setting to preferences dialog --- src/Dialogs/PreferencesDialog.vala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Dialogs/PreferencesDialog.vala b/src/Dialogs/PreferencesDialog.vala index 4f374c9..69bc1f8 100644 --- a/src/Dialogs/PreferencesDialog.vala +++ b/src/Dialogs/PreferencesDialog.vala @@ -48,5 +48,10 @@ public class Gnonograms.Dialogs.Preferences : Granite.Dialog { settings.bind ("columns", column_setting, "value", DEFAULT); settings.bind ("rows", row_setting, "value", DEFAULT); + + grade_setting.selected = settings.get_enum ("grade"); + grade_setting.notify["selected"].connect (() => { + settings.set_enum ("grade", (Difficulty)(grade_setting.selected)); + }); } } From 65fa1841fcba338dd4225b17efda8a874fc2c285 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 5 Nov 2024 20:14:32 +0000 Subject: [PATCH 075/142] Cleanup Preference dialog creation --- src/Controller.vala | 2 +- src/Dialogs/PreferencesDialog.vala | 6 ------ src/HeaderBar/AppPopover.vala | 6 +++++- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index 3c753f8..e4de564 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -25,7 +25,7 @@ public class Gnonograms.Controller : GLib.Object { * must be "Saved As" - which by default is writable. */ public bool is_readonly { get; set; default = false;} - private View view; + public unowned View view {get; construct; } private Model model; private Solver? solver; private SimpleRandomGameGenerator? generator; diff --git a/src/Dialogs/PreferencesDialog.vala b/src/Dialogs/PreferencesDialog.vala index 69bc1f8..e217b3b 100644 --- a/src/Dialogs/PreferencesDialog.vala +++ b/src/Dialogs/PreferencesDialog.vala @@ -7,12 +7,6 @@ public class Gnonograms.Dialogs.Preferences : Granite.Dialog { - public Preferences (Gtk.Window? parent) { - Object ( - title: _("Preferences"), - transient_for: parent - ); - } construct { var grade_setting = new Gtk.DropDown.from_strings ( Difficulty.all_human ()); diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index fb29fdd..3528e3a 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -72,8 +72,12 @@ public class Gnonograms.AppPopover : Gtk.Popover { preferences_button.clicked.connect (() => { popdown (); - var dialog = new Dialogs.Preferences ((Gtk.Window)get_ancestor (typeof (Gtk.Window))); + var dialog = new Dialogs.Preferences () { + transient_for = controller.view, + title = _("Preferences") + }; dialog.response.connect (() => { + // Changes mediated by settings schema dialog.destroy (); }); dialog.present (); From c1a166ea5fa79f74ce8badb3820ceafe608e4352 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 6 Nov 2024 19:14:16 +0000 Subject: [PATCH 076/142] Implement minimum space in clueboxes, based on window size --- libcore/Constants.vala | 2 +- libcore/widgets/Clue.vala | 4 +- libcore/widgets/Cluebox.vala | 85 ++++++++++++++++++++++-------------- src/View.vala | 4 +- 4 files changed, 58 insertions(+), 37 deletions(-) diff --git a/libcore/Constants.vala b/libcore/Constants.vala index e437749..40fbe85 100644 --- a/libcore/Constants.vala +++ b/libcore/Constants.vala @@ -9,7 +9,7 @@ namespace Gnonograms { public const uint MAXSIZE = 54; // max number rows or columns public const uint MINSIZE = 5; // Change to 1 when debugging public const uint SIZESTEP = 5; // Change to 1 when debugging - public const double GRID_LABELBOX_RATIO = 0.3; // For simplicity give labelboxes fixed ratio of cellgrid dimension + public const Difficulty MIN_GRADE = Difficulty.EASY; /* TRIVIAL and VERY EASY GRADES not worth supporting */ public const string BLOCKSEPARATOR = ", "; public const string BLANKLABELTEXT = N_("?"); diff --git a/libcore/widgets/Clue.vala b/libcore/widgets/Clue.vala index 158b2f3..a35bb92 100644 --- a/libcore/widgets/Clue.vala +++ b/libcore/widgets/Clue.vala @@ -25,9 +25,9 @@ class Gnonograms.Clue : Object { private Gee.List clue_blocks; // List of blocks based on clue - public Clue (Gtk.Orientation orientation, ClueBox cluebox) { + public Clue (bool _vertical_text, ClueBox cluebox) { Object ( - vertical_text: orientation == Gtk.Orientation.HORIZONTAL, + vertical_text: _vertical_text, cluebox: cluebox ); } diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index 070cffe..dae6f95 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -4,60 +4,81 @@ * * Authored by: Jeremy Wootten */ -public class Gnonograms.ClueBox : Gtk.Box { +public class Gnonograms.ClueBox : Gtk.Widget { + static construct { + set_layout_manager_type (typeof (Gtk.BinLayout)); + } + + public const double WINDOW_CLUEBOX_RATIO = 0.3; // For simplicity give labelboxes fixed ratio of window dimensions public unowned View view { get; construct; } + public bool holds_column_clues { get; construct; } // The number of cells each clue addresses, monitored by clues public uint n_cells { get; set; default = 0; } - private Gee.ArrayList clues; - private uint height { - get { - return view.controller.rows; - } - } - public ClueBox (Gtk.Orientation _orientation, View view) { + private Gee.ArrayList clues; + public ClueBox (View _view, bool _holds_column_clues) { Object ( - view: view, - orientation: _orientation + view: _view, + holds_column_clues: _holds_column_clues ); } construct { - homogeneous = true; + var orientation = holds_column_clues ? Gtk.Orientation.HORIZONTAL : Gtk.Orientation.VERTICAL; + var layout = new Gtk.BoxLayout (orientation) { + homogeneous = true, + spacing = 0 + }; + set_layout_manager (layout); clues = new Gee.ArrayList (); - view.controller.notify ["rows"].connect (on_dimensions_changed); - view.controller.notify ["columns"].connect (on_dimensions_changed); - on_dimensions_changed (); + if (holds_column_clues) { + set_minimum_height (); + view.notify["default-height"].connect (set_minimum_height); + view.controller.notify ["columns"].connect (add_remove_clues); + } else { + set_minimum_width (); + view.notify["default-width"].connect (set_minimum_width); + view.controller.notify ["rows"].connect (add_remove_clues); + } } - private void on_dimensions_changed () { - if (view.controller.columns == 0 || view.controller.rows == 0) { - return; + private void add_remove_clues () { + var new_n_clues = holds_column_clues ? view.controller.columns : view.controller.rows; + var new_n_cells = holds_column_clues ? view.controller.rows : view.controller.columns; + + if (n_cells != new_n_cells) { + n_cells = new_n_cells; } - var new_n_clues = orientation == Gtk.Orientation.HORIZONTAL ? - view.controller.columns : - view.controller.rows; + if (clues.size != new_n_clues) { + foreach (var clue in clues) { + clue.label.unparent (); + clue.label.destroy (); + } - var new_n_cells = orientation == Gtk.Orientation.HORIZONTAL ? - view.controller.rows : - view.controller.columns; - foreach (var clue in clues) { - remove (clue.label); - } + clues.clear (); - clues.clear (); - n_cells = new_n_cells; - for (int index = 0; index < new_n_clues; index++) { - var clue = new Clue (orientation, this); - clues.add (clue); - append (clue.label); + for (int index = 0; index < new_n_clues; index++) { + var clue = new Clue (holds_column_clues, this); + clues.add (clue); + clue.label.set_parent (this); + } } } + private void set_minimum_width () { + var width = (double) view.default_width * WINDOW_CLUEBOX_RATIO; + set_size_request ((int) width, -1); + } + + private void set_minimum_height () { + var height = (double) view.default_height * WINDOW_CLUEBOX_RATIO; + set_size_request (-1, (int) height); + } + public string[] get_clue_texts () { string[] clue_texts = {}; foreach (var clue in clues) { diff --git a/src/View.vala b/src/View.vala index 74a8828..c8e40c3 100644 --- a/src/View.vala +++ b/src/View.vala @@ -255,8 +255,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { set_titlebar (header_bar); - row_clue_box = new ClueBox (Gtk.Orientation.VERTICAL, this); - column_clue_box = new ClueBox (Gtk.Orientation.HORIZONTAL, this); + row_clue_box = new ClueBox (this, false); + column_clue_box = new ClueBox (this, true); cell_grid = new CellGrid (this); From 922f2f1970d30e7be7c8255ec983ab4ced539bab Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 7 Nov 2024 14:59:53 +0000 Subject: [PATCH 077/142] Ensure labels aligned with cells --- libcore/widgets/Cellgrid.vala | 55 +++++++++++++++++++++-------------- libcore/widgets/Clue.vala | 21 ++++++++++++- libcore/widgets/Cluebox.vala | 19 ++---------- src/View.vala | 3 +- 4 files changed, 58 insertions(+), 40 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 5856495..55ac80c 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -45,8 +45,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { private const double MINOR_GRID_LINE_WIDTH = 1.0; private Gdk.RGBA[, ] colors; - private double cell_width; /* Width and Height of cell including frame */ - private double cell_height; /* Width and Height of cell including frame */ + public int cell_width { get; private set; } /* Width and Height of cell including frame */ + public int cell_height { get; private set; }/* Width and Height of cell including frame */ private bool dirty = false; /* Whether a redraw is needed */ @@ -148,29 +148,40 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } public override void size_allocate (int w, int h, int bl) { - if (view.controller.rows > 0 && view.controller.columns > 0) { - content_width = w; - content_height = h; - cell_width = (double) w / (double) view.controller.columns; - cell_height = (double) h / (double) view.controller.rows; - /* Cause refresh of existing pattern */ - highlight_pattern = new CellPattern.highlight (cell_width, cell_height); - } + var r = (double) view.controller.rows; + var c = (double) view.controller.columns; + var dw = (double) w; + var dh = (double) h; + + double height, width; + if (r > 0 && c > 0) { + if (r > c) { + height = dh; + width = dh * c / r; + } else if (c < r) { + width = dw; + height = dw * r / c; + } else if (h > w) { + width = dw; + height = dw * r / c; + } else { + height = dh; + width = dh * c / r; + } - base.size_allocate (w, h, bl); - } + // Hack needed to allow window to be shrunk + width -= 10.0; + height -= 10.0; - public override void measure (Gtk.Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) { - if (orientation == Gtk.Orientation.HORIZONTAL) { - natural = content_width; - } else { - natural = content_height; + cell_width = (int) (width / c); + cell_height = (int) (height / r); + + content_width = cell_width * (int) view.controller.columns; + content_height = cell_height * (int) view.controller.rows; + + /* Cause refresh of existing pattern */ + highlight_pattern = new CellPattern.highlight (cell_width, cell_height); } - // Allow to shrink - minimum = 0; - // Must not set baseline on non-text widget - minimum_baseline = -1; - natural_baseline = -1; } private void draw_func (Gtk.DrawingArea drawing_area, Cairo.Context cr, int x, int y) { diff --git a/libcore/widgets/Clue.vala b/libcore/widgets/Clue.vala index a35bb92..34d8784 100644 --- a/libcore/widgets/Clue.vala +++ b/libcore/widgets/Clue.vala @@ -45,6 +45,20 @@ class Gnonograms.Clue : Object { label.realize.connect_after (update_markup); cluebox.notify["n_cells"].connect (update_tooltip); cluebox.view.notify["font-scaling"].connect (update_markup); + cluebox.notify["cell-size"].connect (update_size_request); + + update_size_request (); + } + + private void update_size_request () { + var size = cluebox.cell_size; + if (vertical_text) { + label.width_request = (int) size; + label.height_request = (int) (size * (double) cluebox.n_cells / 3.0); + } else { + label.height_request = (int) size; + label.width_request = (int) (size * (double) cluebox.n_cells / 3.0); + } } public void highlight (bool is_highlight) { @@ -221,7 +235,12 @@ class Gnonograms.Clue : Object { attrib = "".printf (weight, strikethrough); sb.append (attrib); - sb.append (clue_block.length.to_string ()); + if (vertical_text) { + sb.append (" " + clue_block.length.to_string () + " "); + } else { + sb.append (clue_block.length.to_string ()); + } + sb.append (""); if (vertical_text) { sb.append ("\n"); diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index dae6f95..4dc947c 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -6,7 +6,7 @@ */ public class Gnonograms.ClueBox : Gtk.Widget { static construct { - set_layout_manager_type (typeof (Gtk.BinLayout)); + set_layout_manager_type (typeof (Gtk.BoxLayout)); } public const double WINDOW_CLUEBOX_RATIO = 0.3; // For simplicity give labelboxes fixed ratio of window dimensions @@ -14,6 +14,7 @@ public class Gnonograms.ClueBox : Gtk.Widget { public bool holds_column_clues { get; construct; } // The number of cells each clue addresses, monitored by clues public uint n_cells { get; set; default = 0; } + public double cell_size { get; set; } private Gee.ArrayList clues; public ClueBox (View _view, bool _holds_column_clues) { @@ -26,7 +27,7 @@ public class Gnonograms.ClueBox : Gtk.Widget { construct { var orientation = holds_column_clues ? Gtk.Orientation.HORIZONTAL : Gtk.Orientation.VERTICAL; var layout = new Gtk.BoxLayout (orientation) { - homogeneous = true, + homogeneous = false, spacing = 0 }; set_layout_manager (layout); @@ -34,12 +35,8 @@ public class Gnonograms.ClueBox : Gtk.Widget { clues = new Gee.ArrayList (); if (holds_column_clues) { - set_minimum_height (); - view.notify["default-height"].connect (set_minimum_height); view.controller.notify ["columns"].connect (add_remove_clues); } else { - set_minimum_width (); - view.notify["default-width"].connect (set_minimum_width); view.controller.notify ["rows"].connect (add_remove_clues); } } @@ -69,16 +66,6 @@ public class Gnonograms.ClueBox : Gtk.Widget { } } - private void set_minimum_width () { - var width = (double) view.default_width * WINDOW_CLUEBOX_RATIO; - set_size_request ((int) width, -1); - } - - private void set_minimum_height () { - var height = (double) view.default_height * WINDOW_CLUEBOX_RATIO; - set_size_request (-1, (int) height); - } - public string[] get_clue_texts () { string[] clue_texts = {}; foreach (var clue in clues) { diff --git a/src/View.vala b/src/View.vala index c8e40c3..1327c03 100644 --- a/src/View.vala +++ b/src/View.vala @@ -259,7 +259,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { column_clue_box = new ClueBox (this, true); cell_grid = new CellGrid (this); - + cell_grid.bind_property ("cell-width", column_clue_box, "cell-size"); + cell_grid.bind_property ("cell-height", row_clue_box, "cell-size"); main_grid = new Gtk.Grid () { focusable = true, // Needed for key controller to work From 57daff63dba3342ed2ee322374cb5b12e55df1bc Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 7 Nov 2024 15:03:29 +0000 Subject: [PATCH 078/142] Comment, tweak --- libcore/widgets/Cellgrid.vala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 55ac80c..53ddd96 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -148,12 +148,13 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } public override void size_allocate (int w, int h, int bl) { + // Avoid unwanted rounding var r = (double) view.controller.rows; var c = (double) view.controller.columns; var dw = (double) w; var dh = (double) h; - double height, width; + // Optimise fit in availabel space, keeping square cells if (r > 0 && c > 0) { if (r > c) { height = dh; @@ -170,12 +171,14 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } // Hack needed to allow window to be shrunk - width -= 10.0; - height -= 10.0; + width -= 6.0; + height -= 6.0; + // Cell width and height should be the same but leave separate for now. cell_width = (int) (width / c); cell_height = (int) (height / r); + // Ensure content dimensions exact multiple of cell dimensions content_width = cell_width * (int) view.controller.columns; content_height = cell_height * (int) view.controller.rows; From 250be6c7deec3ee9628d81e6f635bd2d9aa28a9b Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 8 Nov 2024 09:22:13 +0000 Subject: [PATCH 079/142] Implement show preferences action and shortcut --- libcore/widgets/Cellgrid.vala | 2 +- src/Application.vala | 5 +++- src/Dialogs/PreferencesDialog.vala | 4 ++-- src/HeaderBar/AppPopover.vala | 38 +++++++++++++++++------------- src/View.vala | 34 ++++++++++++++++---------- 5 files changed, 49 insertions(+), 34 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 53ddd96..a5ec723 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -170,7 +170,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { width = dh * c / r; } - // Hack needed to allow window to be shrunk + // Hack needed to allow window to be shrunk and create bottom/end margins width -= 6.0; height -= 6.0; diff --git a/src/Application.vala b/src/Application.vala index 9e217e6..0fcceb2 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -29,13 +29,16 @@ namespace Gnonograms { public const string ACTION_SOLVE = "action-solve"; public const string ACTION_HINT = "action-hint"; public const string ACTION_OPTIONS = "action-options"; + public const string ACTION_OPTIONS_ACCEL = ""; + + public const string ACTION_PREFERENCES = "action-preferences"; #if WITH_DEBUGGING public const string ACTION_DEBUG_ROW = "action-debug-row"; public const string ACTION_DEBUG_COL = "action-debug-col"; #endif public GLib.Settings saved_state; public GLib.Settings settings; - + public class App : Gtk.Application { private Controller controller; diff --git a/src/Dialogs/PreferencesDialog.vala b/src/Dialogs/PreferencesDialog.vala index e217b3b..6064245 100644 --- a/src/Dialogs/PreferencesDialog.vala +++ b/src/Dialogs/PreferencesDialog.vala @@ -6,9 +6,9 @@ */ public class Gnonograms.Dialogs.Preferences : Granite.Dialog { - - construct { + set_default_size (100, 300); + resizable = false; var grade_setting = new Gtk.DropDown.from_strings ( Difficulty.all_human ()); var row_setting = new Gtk.SpinButton ( new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 3528e3a..b7f21cb 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -68,20 +68,7 @@ public class Gnonograms.AppPopover : Gtk.Popover { var load_game_button = new PopoverButton (_("Load"), ACTION_PREFIX + ACTION_OPEN); var save_game_button = new PopoverButton (_("Save"), ACTION_PREFIX + ACTION_SAVE); var save_as_game_button = new PopoverButton (_("Save to Different File"), ACTION_PREFIX + ACTION_SAVE_AS); - var preferences_button = new PopoverButton (_("Preferences")); - - preferences_button.clicked.connect (() => { - popdown (); - var dialog = new Dialogs.Preferences () { - transient_for = controller.view, - title = _("Preferences") - }; - dialog.response.connect (() => { - // Changes mediated by settings schema - dialog.destroy (); - }); - dialog.present (); - }); + var preferences_button = new PopoverButton (_("Preferences"), ACTION_PREFIX + ACTION_PREFERENCES); var settings_box = new Gtk.Box (VERTICAL, 3); settings_box.append (font_size_box); @@ -97,10 +84,13 @@ public class Gnonograms.AppPopover : Gtk.Popover { } private class PopoverButton : Gtk.Button { - public PopoverButton (string label, string? action_name = null) { + public string text { get; construct; } + public string detailed_action { get; construct; } + + public PopoverButton (string _text, string? _action_name = null) { Object ( - child: new Gtk.Label (label) {xalign = 0.0f}, - action_name: action_name + text: _text, + detailed_action: _action_name // Assigning directly to Gtk.Button.action_name doesnt work for some reason ); } @@ -108,6 +98,20 @@ public class Gnonograms.AppPopover : Gtk.Popover { margin_top = 3; margin_bottom = 3; add_css_class (Granite.STYLE_CLASS_FLAT); + set_action_name (detailed_action); + if (text != null && detailed_action != null) { + var accels = ((Gtk.Application) Application.get_default ()).get_accels_for_action (detailed_action); + if (accels != null) { + warning ("got accels"); + child = new Granite.AccelLabel (text, accels[0]); + return; + } else { + warning ("No accels for %s", detailed_action); + } + } + + warning ("fallback"); + child = new Gtk.Label (text); } } } diff --git a/src/View.vala b/src/View.vala index 1327c03..b1b13e3 100644 --- a/src/View.vala +++ b/src/View.vala @@ -33,7 +33,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { {ACTION_RESTART, action_restart}, {ACTION_SOLVE, action_solve}, {ACTION_HINT, action_hint}, - {ACTION_OPTIONS, action_options} + {ACTION_OPTIONS, action_options}, + {ACTION_PREFERENCES, action_preferences} }; #if WITH_DEBUGGING @@ -67,12 +68,12 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private Adw.ToastOverlay toast_overlay; private Gtk.Stack progress_stack; private Gtk.Label title_label; - private Gtk.Label grade_label; private Gtk.Button generate_button; private Gtk.Button undo_button; private Gtk.Button redo_button; private Gtk.Button check_correct_button; private Gtk.Button hint_button; + private AppPopover app_popover; // private Gtk.Button auto_solve_button; private Gtk.Button restart_button; private uint drawing_with_key; @@ -122,6 +123,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { action_accelerators.set (ACTION_SOLVE, "S"); action_accelerators.set (ACTION_OPTIONS, "F10"); action_accelerators.set (ACTION_OPTIONS, "Menu"); + action_accelerators.set (ACTION_PREFERENCES, "P"); #if WITH_DEBUGGING action_accelerators.set (ACTION_DEBUG_ROW, "R"); action_accelerators.set (ACTION_DEBUG_COL, "C"); @@ -193,7 +195,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { _("Generate New Puzzle") ); - var app_popover = new AppPopover (controller); + app_popover = new AppPopover (controller); menu_button = new Gtk.MenuButton () { tooltip_markup = Granite.markup_accel_tooltip ( @@ -203,7 +205,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { ), icon_name = "open-menu-symbolic", valign = Gtk.Align.CENTER, - popover = new AppPopover (controller) + popover = app_popover }; // Unable to set markup on Granite.ModeSwitch so fake a Granite accelerator tooltip for now. @@ -226,12 +228,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { }; title_label.add_css_class (Granite.STYLE_CLASS_H3_LABEL); - grade_label = new Gtk.Label ("Easy") { - use_markup = true, - xalign = 0.5f - }; - grade_label.add_css_class (Granite.STYLE_CLASS_H4_LABEL); - progress_stack = new Gtk.Stack () { halign = Gtk.Align.CENTER, }; @@ -449,7 +445,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } if (game_grade != Difficulty.UNDEFINED) { - progress_stack.set_visible_child_name ("Grade"); + progress_stack.set_visible_child_name ("Title"); } else { progress_stack.set_visible_child_name ("None"); } @@ -481,9 +477,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public void update_title () { title_label.label = game_name; title_label.tooltip_text = controller.current_game_path; - grade_label.label = game_grade.to_string (); if (game_grade != Difficulty.UNDEFINED) { - progress_stack.set_visible_child_name ("Grade"); + progress_stack.set_visible_child_name ("Title"); } else { progress_stack.set_visible_child_name ("None"); } @@ -619,6 +614,19 @@ public class Gnonograms.View : Gtk.ApplicationWindow { menu_button.activate (); } + private void action_preferences () { + app_popover.popdown (); + var dialog = new Dialogs.Preferences () { + transient_for = this, + title = _("Preferences") + }; + dialog.response.connect (() => { + // Changes mediated by settings schema + dialog.destroy (); + }); + dialog.present (); + } + #if WITH_DEBUGGING private void action_debug_row () { debug_request (current_cell.row, false); From bfa99cef1ad85ca72cc9cd53c60d2c36204060f8 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 10 Nov 2024 15:57:25 +0000 Subject: [PATCH 080/142] Align labels with grid better; lose font scale control --- ...com.github.jeremypw.gnonograms.gschema.xml | 8 -- libcore/widgets/Cellgrid.vala | 114 ++++++++---------- libcore/widgets/Clue.vala | 23 +--- libcore/widgets/Cluebox.vala | 45 ++++++- src/Controller.vala | 9 +- src/HeaderBar/AppPopover.vala | 49 -------- src/View.vala | 1 - 7 files changed, 105 insertions(+), 144 deletions(-) diff --git a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml index 8edc557..012b3cd 100644 --- a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml +++ b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml @@ -68,14 +68,6 @@ Defaults to yellow - - - 100 - Scaling of label text - - The size of the clue label text relative to system text. - - diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index a5ec723..22cdc5f 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -45,8 +45,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { private const double MINOR_GRID_LINE_WIDTH = 1.0; private Gdk.RGBA[, ] colors; - public int cell_width { get; private set; } /* Width and Height of cell including frame */ - public int cell_height { get; private set; }/* Width and Height of cell including frame */ + public double cell_width { get; private set; } /* Width and Height of cell including frame */ + public double cell_height { get; private set; }/* Width and Height of cell including frame */ private bool dirty = false; /* Whether a redraw is needed */ @@ -148,43 +148,38 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } public override void size_allocate (int w, int h, int bl) { - // Avoid unwanted rounding var r = (double) view.controller.rows; var c = (double) view.controller.columns; - var dw = (double) w; - var dh = (double) h; + // Need to allow window to be shrunk and create bottom/end margins + var dw = (double) w - c - 12; + var dh = (double) h - r - 12; + if (r == 0 || c == 0) { + return; + } + var width_for_height = dh * c / r; + var height_for_width = dw * r / c; + //Calculate content dimensions, optimise fit in available space, keeping square cells double height, width; - // Optimise fit in availabel space, keeping square cells - if (r > 0 && c > 0) { - if (r > c) { - height = dh; - width = dh * c / r; - } else if (c < r) { - width = dw; - height = dw * r / c; - } else if (h > w) { - width = dw; - height = dw * r / c; - } else { - height = dh; - width = dh * c / r; - } - - // Hack needed to allow window to be shrunk and create bottom/end margins - width -= 6.0; - height -= 6.0; + if (width_for_height > dw) { + width = dw; + height = height_for_width; + } else if (height_for_width > dh) { + height = dh; + width = width_for_height; + } else { + height = dh; + width = dw; + } - // Cell width and height should be the same but leave separate for now. - cell_width = (int) (width / c); - cell_height = (int) (height / r); + // Cell width and height should be the same but leave separate for now. + cell_width = width / c; + cell_height = height / r; - // Ensure content dimensions exact multiple of cell dimensions - content_width = cell_width * (int) view.controller.columns; - content_height = cell_height * (int) view.controller.rows; + content_width = (int) width; + content_height = (int) height; - /* Cause refresh of existing pattern */ - highlight_pattern = new CellPattern.highlight (cell_width, cell_height); - } + /* Cause refresh of existing pattern */ + highlight_pattern = new CellPattern.highlight (cell_width, cell_height); } private void draw_func (Gtk.DrawingArea drawing_area, Cairo.Context cr, int x, int y) { @@ -233,54 +228,51 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { cr.set_antialias (Cairo.Antialias.NONE); cr.set_line_width (MINOR_GRID_LINE_WIDTH); + var r = view.controller.rows; + var c = view.controller.columns; + var w = cell_width; + var h = cell_height; // Draw minor grid lines - double y1 = 0; - double x1 = 0; - double x2 = content_width - 1; - double y2 = content_height - 1; - while (y1 < y2) { - cr.move_to (x1, y1); + var x2 = w * c; + var y2 = h * r; + // Draw horizontal lines + for (int cell = 0; cell < r; cell++) { + var y1 = cell * h; + cr.move_to (0, y1); cr.line_to (x2, y1); cr.stroke (); - y1 += cell_height; } - y1 = 0; - - while (x1 < x2) { - cr.move_to (x1, y1); + // Draw vertical lines + for (int cell = 0; cell < c; cell++) { + var x1 = cell * w; + cr.move_to (x1, 0); cr.line_to (x1, y2); cr.stroke (); - x1 += cell_width; } - x1 = 0; - // Draw inner major grid lines cr.set_line_width (MAJOR_GRID_LINE_WIDTH); - var increment = 5.0 * cell_height; - y1 = increment; - while (y1 < y2) { - cr.move_to (x1, y1); + // Draw horizontal lines + for (int cell = 5; cell < r; cell += 5) { + var y1 = cell * h; + cr.move_to (0, y1); cr.line_to (x2, y1); cr.stroke (); - y1 += increment; } - y1 = MINOR_GRID_LINE_WIDTH; - increment = 5.0 * cell_width; - x1 = increment; - while (x1 < x2) { - cr.move_to (x1, y1); + // Draw vertical lines + for (int cell = 5; cell < c; cell += 5) { + var x1 = cell * w; + cr.move_to (x1, 0); cr.line_to (x1, y2); cr.stroke (); - x1 += increment; } // Draw frame cr.set_line_width (MINOR_GRID_LINE_WIDTH); - y1 = MINOR_GRID_LINE_WIDTH; - x1 = MINOR_GRID_LINE_WIDTH; + var y1 = MINOR_GRID_LINE_WIDTH; + var x1 = MINOR_GRID_LINE_WIDTH; cr.move_to (x1, y1); cr.line_to (x2 - x1, y1); cr.stroke (); @@ -382,7 +374,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { construct { var r = double.min (width, height) / 2.0; - var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, (int)width, (int)height); + var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, (int)width , (int)height); var context = new Cairo.Context (surface); context.set_source_rgb (0.0, 0.0, 0.0); context.rectangle (0, 0, width, height); diff --git a/libcore/widgets/Clue.vala b/libcore/widgets/Clue.vala index 34d8784..b939eba 100644 --- a/libcore/widgets/Clue.vala +++ b/libcore/widgets/Clue.vala @@ -37,28 +37,13 @@ class Gnonograms.Clue : Object { xalign = _vertical_text ? (float)0.5 : (float)1.0, yalign = vertical_text ? (float)1.0 : (float)0.5, has_tooltip = true, - use_markup = true + use_markup = true, }; text = "0"; label.realize.connect_after (update_markup); - cluebox.notify["n_cells"].connect (update_tooltip); - cluebox.view.notify["font-scaling"].connect (update_markup); - cluebox.notify["cell-size"].connect (update_size_request); - - update_size_request (); - } - - private void update_size_request () { - var size = cluebox.cell_size; - if (vertical_text) { - label.width_request = (int) size; - label.height_request = (int) (size * (double) cluebox.n_cells / 3.0); - } else { - label.height_request = (int) size; - label.width_request = (int) (size * (double) cluebox.n_cells / 3.0); - } + cluebox.notify["cell-size"].connect (update_markup); } public void highlight (bool is_highlight) { @@ -204,12 +189,12 @@ class Gnonograms.Clue : Object { } private void update_markup () { - label.set_markup ("".printf (cluebox.view.font_scaling) + get_markup () + ""); + label.set_markup ("".printf (cluebox.font_desc.to_string ()) + get_markup () + ""); update_tooltip (); } private void update_tooltip () { - label.set_tooltip_markup ("".printf (cluebox.view.font_scaling) + + label.set_tooltip_markup ("".printf (cluebox.font_desc.to_string ()) + _("Freedom = %u").printf (cluebox.n_cells - Utils.blockextent_from_clue (_text)) + "" ); diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index 4dc947c..2f26acc 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -9,12 +9,13 @@ public class Gnonograms.ClueBox : Gtk.Widget { set_layout_manager_type (typeof (Gtk.BoxLayout)); } - public const double WINDOW_CLUEBOX_RATIO = 0.3; // For simplicity give labelboxes fixed ratio of window dimensions + const int PIX_TO_PANGO_FONT = 1024 / 2; + public unowned View view { get; construct; } public bool holds_column_clues { get; construct; } - // The number of cells each clue addresses, monitored by clues - public uint n_cells { get; set; default = 0; } + public uint n_cells { get; set; default = 0; }// The number of cells each clue addresses, monitored by clues public double cell_size { get; set; } + public Pango.FontDescription font_desc { get; set; } private Gee.ArrayList clues; public ClueBox (View _view, bool _holds_column_clues) { @@ -32,15 +33,51 @@ public class Gnonograms.ClueBox : Gtk.Widget { }; set_layout_manager (layout); + margin_bottom = holds_column_clues ? 0 : 6; + margin_end = holds_column_clues ? 6 : 0; clues = new Gee.ArrayList (); + font_desc = Pango.FontDescription.from_string ("Arial 10"); + var mode = holds_column_clues ? Gtk.SizeGroupMode.HORIZONTAL : Gtk.SizeGroupMode.VERTICAL; if (holds_column_clues) { + hexpand = false; view.controller.notify ["columns"].connect (add_remove_clues); } else { + vexpand = false; view.controller.notify ["rows"].connect (add_remove_clues); } - } + notify["cell-size"].connect (() => { + font_desc.set_absolute_size (cell_size * PIX_TO_PANGO_FONT); + var index = 0.0; + var size = (int) cell_size; + var diff = cell_size - (double) size; + var shortfall = 0.0; + // Assign label widths to match grid lines as closely as possible. + // As the cell dimensions are non-integral we have to vary the (integral) label widths + foreach (Clue clue in clues) { + var makeup = 0; + if (shortfall >= 1.0) { + makeup = 1; + shortfall-= 1.0; + } + + var label = clue.label; + if (holds_column_clues) { + label.width_request = size + makeup; + label.height_request = (int) (cell_size * (double) n_cells / 3.0); + } else { + label.height_request = size + makeup; + label.width_request = (int) (cell_size * (double) n_cells / 3.0); + } + + index++; + shortfall += diff; + } + }); + + + } private void add_remove_clues () { var new_n_clues = holds_column_clues ? view.controller.columns : view.controller.rows; diff --git a/src/Controller.vala b/src/Controller.vala index e4de564..f869580 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -103,8 +103,13 @@ public class Gnonograms.Controller : GLib.Object { /* * This is very finicky. Bind size after present else set_titlebar gives us bad sizes */ - saved_state.bind ("window-height", view, "default-height", SettingsBindFlags.DEFAULT); - saved_state.bind ("window-width", view, "default-width", SettingsBindFlags.DEFAULT); + + // TODO limit related to actual monitor dimensions + view.default_height = saved_state.get_int ("window-height").clamp (64, 768); + view.default_width = saved_state.get_int ("window-width").clamp (128, 1024); + + saved_state.bind ("window-height", view, "default-height", SettingsBindFlags.SET); + saved_state.bind ("window-width", view, "default-width", SettingsBindFlags.SET); bind_property ( "generator-grade", diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index b7f21cb..c058f9b 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -12,54 +12,6 @@ public class Gnonograms.AppPopover : Gtk.Popover { ); } construct { - var zoom_out_button = new Gtk.Button.from_icon_name ("zoom-out-symbolic"); - zoom_out_button.tooltip_markup = Granite.markup_accel_tooltip ( - {"minus"}, - _("Zoom Out") - ); - zoom_out_button.clicked.connect (() => { - var current_font_scale = settings.get_int ("font-scaling"); - settings.set_int ("font-scaling", current_font_scale - 10); - }); - - var zoom_in_button = new Gtk.Button.from_icon_name ("zoom-in-symbolic"); - zoom_in_button.tooltip_markup = Granite.markup_accel_tooltip ( - {"plus"}, - _("Zoom In") - ); - zoom_in_button.clicked.connect (() => { - var current_font_scale = settings.get_int ("font-scaling"); - settings.set_int ("font-scaling", current_font_scale + 10); - }); - - var zoom_default_button = new Gtk.Button () { - label = settings.get_int ("font-scaling").to_string () + "%" - }; - - zoom_default_button.tooltip_markup = Granite.markup_accel_tooltip ( - {"0"}, - _("Zoom Default") - ); - zoom_default_button.clicked.connect (() => { - var current_font_scale = settings.get_int ("font-scaling"); - settings.set_int ("font-scaling", 100); - }); - settings.changed["font-scaling"].connect (() => { - zoom_default_button.label = settings.get_int ("font-scaling").to_string () + "%"; - }); - - var font_size_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) { - homogeneous = true, - hexpand = true, - margin_top = 12, - margin_start = 12, - margin_end = 12, - }; - font_size_box.add_css_class (Granite.STYLE_CLASS_LINKED); - font_size_box.append (zoom_out_button); - font_size_box.append (zoom_default_button); - font_size_box.append (zoom_in_button); - var title_entry = new Gtk.Entry () { placeholder_text = _("Enter title of game here"), margin_top = 12, @@ -71,7 +23,6 @@ public class Gnonograms.AppPopover : Gtk.Popover { var preferences_button = new PopoverButton (_("Preferences"), ACTION_PREFIX + ACTION_PREFERENCES); var settings_box = new Gtk.Box (VERTICAL, 3); - settings_box.append (font_size_box); settings_box.append (title_entry); settings_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); settings_box.append (load_game_button); diff --git a/src/View.vala b/src/View.vala index b1b13e3..172371a 100644 --- a/src/View.vala +++ b/src/View.vala @@ -47,7 +47,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public Cell previous_cell { get; set; } public Difficulty generator_grade { get; set; } public Difficulty game_grade { get; set; default = Difficulty.UNDEFINED;} - public int font_scaling { get; set; default = 100; } // Percentage font scaling for clue labels public string game_name { get { return controller.game_name; } } public bool strikeout_complete { get; set; } public bool readonly { get; set; default = false;} From d539312aeaf9a1799f0d2aef7e9b83a36a02891d Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 10 Nov 2024 16:08:55 +0000 Subject: [PATCH 081/142] Move hint button to start of header bar --- src/View.vala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/View.vala b/src/View.vala index 172371a..9494ab0 100644 --- a/src/View.vala +++ b/src/View.vala @@ -142,8 +142,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { gtk_settings.gtk_application_prefer_dark_theme = prefer_dark; }); - settings.bind ("font-scaling", this, "font-scaling", SettingsBindFlags.DEFAULT); - var view_actions = new GLib.SimpleActionGroup (); view_actions.add_action_entries (view_action_entries, this); insert_action_group (ACTION_GROUP, view_actions); @@ -240,13 +238,14 @@ public class Gnonograms.View : Gtk.ApplicationWindow { }; header_bar.add_css_class ("gnonograms-header"); header_bar.pack_start (generate_button); + header_bar.pack_start (hint_button); header_bar.pack_start (restart_button); header_bar.pack_start (undo_button); header_bar.pack_start (redo_button); header_bar.pack_start (check_correct_button); header_bar.pack_end (menu_button); header_bar.pack_end (mode_switch); - header_bar.pack_end (hint_button); + set_titlebar (header_bar); From f7109aceff0c96889472933ebc80989efc2b6527 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 10 Nov 2024 16:17:45 +0000 Subject: [PATCH 082/142] Fix open file --- libcore/Filereader.vala | 1 - libcore/utils.vala | 2 +- src/Application.vala | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/libcore/Filereader.vala b/libcore/Filereader.vala index cbe66d1..a8632dd 100644 --- a/libcore/Filereader.vala +++ b/libcore/Filereader.vala @@ -76,7 +76,6 @@ public class Gnonograms.Filereader : Object { load_dir_path, "" ); - } private void parse_gnonogram_game_file (DataInputStream stream) throws GLib.IOError { diff --git a/libcore/utils.vala b/libcore/utils.vala index 230ce09..5d8b4ad 100644 --- a/libcore/utils.vala +++ b/libcore/utils.vala @@ -325,7 +325,7 @@ namespace Gnonograms.Utils { if (save) { result = yield (dialog.save (parent, null)); } else { - result = yield (dialog.save (parent, null)); + result = yield (dialog.open (parent, null)); } diff --git a/src/Application.vala b/src/Application.vala index 0fcceb2..0a370c2 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -67,8 +67,6 @@ namespace Gnonograms { add_action (quit_action); set_accels_for_action ("app.quit", {"q"}); - - } public override void open (File[] files, string hint) { From 70119505350c9233a2da7bde85eff05a8b96f2cc Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 10 Nov 2024 16:28:57 +0000 Subject: [PATCH 083/142] Update screenshot headings --- ....github.jeremypw.gnonograms.appdata.xml.in | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/data/com.github.jeremypw.gnonograms.appdata.xml.in b/data/com.github.jeremypw.gnonograms.appdata.xml.in index c4be02e..7c46086 100644 --- a/data/com.github.jeremypw.gnonograms.appdata.xml.in +++ b/data/com.github.jeremypw.gnonograms.appdata.xml.in @@ -19,6 +19,24 @@ Game LogicGame + + + Solving a puzzle + https://raw.githubusercontent.com/jeremypw/gnonograms/master/data/screenshots/GnonogramsSolvingLight.png + + + Solving a puzzle (dark variant) + https://raw.githubusercontent.com/jeremypw/gnonograms/master/data/screenshots/GnonogramsSolvingDark.png + + + Designing a puzzle + https://raw.githubusercontent.com/jeremypw/gnonograms/master/data/screenshots/GnonogramsDesigningLight.png + + + Designing a puzzle (dark variant) + https://raw.githubusercontent.com/jeremypw/gnonograms/master/data/screenshots/GnonogramsDesigningDark.png + + @@ -202,24 +220,6 @@ com.github.jeremypw.gnonograms libgnonograms-core - - - Manually solving a puzzle - https://raw.githubusercontent.com/jeremypw/gnonograms/master/data/screenshots/GnonogramsSolvingLight.png - - - Manually solving a puzzle - https://raw.githubusercontent.com/jeremypw/gnonograms/master/data/screenshots/GnonogramsSolvingDark.png - - - Manually solving a puzzle - https://raw.githubusercontent.com/jeremypw/gnonograms/master/data/screenshots/GnonogramsDesigningLight.png - - - Manually solving a puzzle - https://raw.githubusercontent.com/jeremypw/gnonograms/master/data/screenshots/GnonogramsDesigningDark.png - - Jeremy Paul Wootten https://github.com/jeremypw/gnonograms https://github.com/jeremypw/gnonograms/issues From 5381205e7384ed430476709bf5b34745046a8c73 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 10 Nov 2024 17:12:49 +0000 Subject: [PATCH 084/142] Update Designing screenshots --- data/screenshots/GnonogramsDesigningDark.png | Bin 57709 -> 39759 bytes data/screenshots/GnonogramsDesigningLight.png | Bin 56947 -> 40659 bytes data/screenshots/GnonogramsSolvingDark.png | Bin 81631 -> 0 bytes data/screenshots/GnonogramsSolvingLight.png | Bin 81760 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 data/screenshots/GnonogramsSolvingDark.png delete mode 100644 data/screenshots/GnonogramsSolvingLight.png diff --git a/data/screenshots/GnonogramsDesigningDark.png b/data/screenshots/GnonogramsDesigningDark.png index bbe4c36e2011d87448e24fcf9fad203de253d4cb..0a3eea170a8cc2074803b0b9a046fb50205dfef4 100644 GIT binary patch literal 39759 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 Ye05>ft_3z4;RA`^vVSvW_KF?<3&EFzCIA2c literal 57709 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 z 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 literal 56947 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˂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

%VKGsbTV>v9Gd=rGmn?>jb&*3$(*8Nv)~5-Ro4v z1=TW>yS=N3c7F>pAT@cu;yJ$OGURT zJQryYykC{Rjjd$b&$!<}omx&wo$d5B_Z#l)_|eugBsO<4y@T8Hrtih|*1D`wok_p4 z6yg#mQe=vj)YY*`K)HOk@3I-`y9~UMb&a=-7;C%=G#aMfE8hpYv9v z#0i-sTbXab2Qn1Hcb3(5Ve#vp=NC8dGmnH0$cK4M&@L%PQfK`_b`6+hD2F}CvlI2d zTAj13%qDLk*ULGLv_*aV{L~Fwr+krfW!!m<_g+3?r9nR4s(O9rt~kCEw&sg)d>5Es z{hT@#59i1uB~82gIkq=`x_V~wr+`Z``K^PF&+lp!O`O5!W%JHzKgx91b9(1IMC?*Dh&HS<10CMP8Bq)Y2+zJ@w-{tJl*u#?17Xr6npw-?QlY^CaqC@MzQAbE@GD zNB)zm&jU^-+O5b`q5iq?-u2G~^{1Th}@Z#193}f^^{*Hhe&v%Z83E?>bL*j e{LkZH&-kQ{g|~Qnb3Rdg6@812MvS_#=l=sDAuZSd diff --git a/data/screenshots/GnonogramsSolvingDark.png b/data/screenshots/GnonogramsSolvingDark.png deleted file mode 100644 index 2d2441c6472fcb1c7bd6f50d810bdef36595bc3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81631 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

L&vF><;#CB?LP&QP`5Suc53)TU!U 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

b#V6 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 H-N*kAHWmo0 From 3bd05bb35868a5018d52471d14c324c48ef6ced6 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 10 Nov 2024 17:13:44 +0000 Subject: [PATCH 085/142] Add Designing game .gno file --- data/screenshots/GnonogramsDesigningGame.gno | 81 ++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 data/screenshots/GnonogramsDesigningGame.gno diff --git a/data/screenshots/GnonogramsDesigningGame.gno b/data/screenshots/GnonogramsDesigningGame.gno new file mode 100644 index 0000000..f948834 --- /dev/null +++ b/data/screenshots/GnonogramsDesigningGame.gno @@ -0,0 +1,81 @@ +[Description] +Rhino +Unknown +2010/07/01 +13 +[Dimensions] +20 +30 +[Row clues] +3 +12 +11,3 +7,3 +2,3,2,2 +3,1,3,5 +3,1,3,2 +3,1,1,1,5 +3,3,1,1,1,4 +3,5,2,1,5 +1,2,7,1,5 +1,1,6,1,8 +1,1,6,10 +1,2,6,10,1 +2,2,3,2,4,5 +1,2,1,2,9 +1,2,1,3,7 +1,2,2,3,5 +2,2,1,3,3 +1,2,1,3 +[Column clues] +8 +5 +7,6 +3,5,2 +1 +2 +2,4,4 +2,2,8 +2,6,2 +3,5 +2,6 +2,6 +2,8 +2,5,3 +2,2 +2 +2,5 +2,2,8 +2,2,5,4 +1,3,1,4,2 +2,4,8 +2,12 +4,12 +3,7,4 +14 +3,7 +2,2,3 +1,2 +1 +1 +[Solution] +1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 1 1 1 1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 +1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 2 2 2 1 1 1 1 1 1 1 +1 1 1 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 2 2 2 1 1 1 1 1 1 +1 1 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 1 2 2 1 2 2 1 1 1 +1 2 2 2 1 1 2 1 1 1 1 1 1 1 1 1 1 1 2 2 2 1 2 2 2 2 2 1 1 1 +2 2 2 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 1 1 2 2 1 1 1 1 +2 2 2 1 1 1 2 1 1 1 1 1 1 2 1 1 1 2 1 1 2 2 2 2 2 1 1 1 1 1 +2 2 2 1 1 1 2 2 2 1 1 1 1 2 1 1 1 2 1 2 1 2 2 2 2 1 1 1 1 1 +2 2 2 1 1 1 1 2 2 2 2 2 1 2 2 1 1 1 2 1 2 2 2 2 2 1 1 1 1 1 +2 1 2 2 1 1 1 1 2 2 2 2 2 2 2 1 1 1 2 1 2 2 2 2 2 1 1 1 1 1 +2 1 1 2 1 1 1 1 2 2 2 2 2 2 1 1 1 1 2 1 2 2 2 2 2 2 2 2 1 1 +2 1 1 2 1 1 1 2 2 2 2 2 2 1 1 1 1 2 2 2 2 2 2 2 2 2 2 1 1 1 +2 1 2 2 1 1 1 2 2 2 2 2 2 1 1 1 2 2 2 2 2 2 2 2 2 2 1 1 1 2 +1 1 2 2 1 1 2 2 1 1 2 2 2 1 1 1 2 2 1 2 2 2 2 1 2 2 2 2 2 1 +1 1 2 1 1 1 2 2 1 1 1 1 2 1 1 1 2 2 1 2 2 2 2 2 2 2 2 2 1 1 +1 1 2 1 1 1 2 2 1 1 1 1 2 1 1 1 2 2 2 1 2 2 2 2 2 2 2 1 1 1 +1 1 2 1 1 1 2 2 1 1 1 1 2 2 1 1 2 2 2 1 1 2 2 2 2 2 1 1 1 1 +1 1 2 2 1 1 1 2 2 1 1 1 1 2 1 1 1 2 2 2 1 1 2 2 2 1 1 1 1 1 +1 1 1 2 1 1 1 2 2 1 1 1 1 2 1 1 1 2 2 2 1 1 1 1 1 1 1 1 1 1 From 2e0836aa8c7dbfb86adf34fd404b236b5add08bd Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 10 Nov 2024 17:17:49 +0000 Subject: [PATCH 086/142] Add solve button to app popover --- src/HeaderBar/AppPopover.vala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index c058f9b..ad0dbe8 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -21,6 +21,7 @@ public class Gnonograms.AppPopover : Gtk.Popover { var save_game_button = new PopoverButton (_("Save"), ACTION_PREFIX + ACTION_SAVE); var save_as_game_button = new PopoverButton (_("Save to Different File"), ACTION_PREFIX + ACTION_SAVE_AS); var preferences_button = new PopoverButton (_("Preferences"), ACTION_PREFIX + ACTION_PREFERENCES); + var solve_button = new PopoverButton (_("Solve"), ACTION_PREFIX + ACTION_SOLVE); var settings_box = new Gtk.Box (VERTICAL, 3); settings_box.append (title_entry); @@ -29,6 +30,8 @@ public class Gnonograms.AppPopover : Gtk.Popover { settings_box.append (save_game_button); settings_box.append (save_as_game_button); settings_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); + settings_box.append (solve_button); + settings_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); settings_box.append (preferences_button); child = settings_box; From ca300cf9ffdd7896e9c7f7bd9a330765365f34ba Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 10 Nov 2024 17:25:43 +0000 Subject: [PATCH 087/142] Update Solving screenshots --- data/screenshots/GnonogramsSolvingDark.png | Bin 0 -> 40969 bytes data/screenshots/GnonogramsSolvingLight.png | Bin 0 -> 41133 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 data/screenshots/GnonogramsSolvingDark.png create mode 100644 data/screenshots/GnonogramsSolvingLight.png diff --git a/data/screenshots/GnonogramsSolvingDark.png b/data/screenshots/GnonogramsSolvingDark.png new file mode 100644 index 0000000000000000000000000000000000000000..06395e61c64d4caed2ba473e3ced7707007b7ab8 GIT binary patch literal 40969 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 literal 0 HcmV?d00001 From 73f1ef77f005a2d595ae1721e896f2db141239bb Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 10 Nov 2024 18:00:49 +0000 Subject: [PATCH 088/142] Layout PreferencesDialog better --- src/Dialogs/PreferencesDialog.vala | 49 ++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/src/Dialogs/PreferencesDialog.vala b/src/Dialogs/PreferencesDialog.vala index 6064245..4c6db7e 100644 --- a/src/Dialogs/PreferencesDialog.vala +++ b/src/Dialogs/PreferencesDialog.vala @@ -7,9 +7,11 @@ public class Gnonograms.Dialogs.Preferences : Granite.Dialog { construct { - set_default_size (100, 300); + set_default_size (400, 100); resizable = false; var grade_setting = new Gtk.DropDown.from_strings ( Difficulty.all_human ()); + var grade_preference = new PreferenceRow (_("Degree of difficulty"), grade_setting); + var row_setting = new Gtk.SpinButton ( new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), 5.0, @@ -20,6 +22,8 @@ public class Gnonograms.Dialogs.Preferences : Granite.Dialog { width_chars = 3, }; + var row_preference = new PreferenceRow (_("Rows"), row_setting); + var column_setting = new Gtk.SpinButton ( new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), 5.0, @@ -27,15 +31,19 @@ public class Gnonograms.Dialogs.Preferences : Granite.Dialog { ) { snap_to_ticks = true, orientation = Gtk.Orientation.HORIZONTAL, - width_chars = 3, + width_chars = 3 }; + var column_preference = new PreferenceRow (_("Columns"), column_setting); //TODO Add Clue help switch - var main_box = new Gtk.Box (VERTICAL, 12); - main_box.append (grade_setting); - main_box.append (row_setting); - main_box.append (column_setting); + var main_box = new Gtk.Box (VERTICAL, 12) { + margin_start = 12, + margin_end = 12 + }; + main_box.append (grade_preference); + main_box.append (row_preference); + main_box.append (column_preference); get_content_area ().append (main_box); add_button (_("Close"), Gtk.ResponseType.APPLY); @@ -48,4 +56,33 @@ public class Gnonograms.Dialogs.Preferences : Granite.Dialog { settings.set_enum ("grade", (Difficulty)(grade_setting.selected)); }); } + + private class PreferenceRow : Gtk.Box { + public string text { get; construct; } + public Gtk.Widget widget { get; construct; } + public PreferenceRow (string text, Gtk.Widget setting_widget) { + Object ( + text: text, + widget: setting_widget + ); + } + + construct { + orientation = Gtk.Orientation.HORIZONTAL; + margin_top = 3; + margin_bottom = 6; + spacing = 12; + hexpand = true; + + var label = new Gtk.Label (text) { + halign = Gtk.Align.START + }; + + widget.halign = Gtk.Align.END; + widget.hexpand = true; + + append (label); + append (widget); + } + } } From 0e371f9d3b15f4eddddbc7fbfd9b98e6ea27df54 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 10 Nov 2024 19:23:25 +0000 Subject: [PATCH 089/142] Add color buttons to preferences dialog --- ...com.github.jeremypw.gnonograms.gschema.xml | 4 +- src/Dialogs/PreferencesDialog.vala | 39 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml index 012b3cd..f810434 100644 --- a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml +++ b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml @@ -52,7 +52,7 @@ - "rgb(24,18,151)" + "rgba(24,18,151,1.0)"

Color of filled cell when solving The color used to mark a filled cell when solving a gnonogram puzzle. @@ -61,7 +61,7 @@ - "rgb(255,255,0)" + "rgba(255,255,0,1.0)" Color of empty cell when solving The color used to mark a empty cell when solving a gnonogram puzzle. diff --git a/src/Dialogs/PreferencesDialog.vala b/src/Dialogs/PreferencesDialog.vala index 4c6db7e..eb03b67 100644 --- a/src/Dialogs/PreferencesDialog.vala +++ b/src/Dialogs/PreferencesDialog.vala @@ -37,13 +37,52 @@ public class Gnonograms.Dialogs.Preferences : Granite.Dialog { var column_preference = new PreferenceRow (_("Columns"), column_setting); //TODO Add Clue help switch + var empty_color_dialog = new Gtk.ColorDialog () { + title = _("Filled Color"), + with_alpha = true + }; + var empty_color_button = new Gtk.ColorDialogButton (empty_color_dialog); + var empty_color = settings.get_string ("empty-color"); + var rgba = Gdk.RGBA (); + if (rgba.parse (empty_color)) { + warning ("empty_colorr setting is %s", rgba.to_string ()); + empty_color_button.set_rgba (rgba); + } + + empty_color_button.notify["rgba"].connect (() => { + warning ("empty color now %s", empty_color_button.get_rgba ().to_string ()); + settings.set_string ("empty-color", empty_color_button.get_rgba ().to_string ()); + }); + var empty_color_preference = new PreferenceRow (_("Color of empty cells"), empty_color_button); + + var filled_color_dialog = new Gtk.ColorDialog () { + title = _("Filled Color"), + with_alpha = true + }; + var filled_color_button = new Gtk.ColorDialogButton (filled_color_dialog); + var filled_color = settings.get_string ("filled-color"); + if (rgba.parse (filled_color)) { + filled_color_button.set_rgba (rgba); + } + + filled_color_button.notify["rgba"].connect (() => { + warning ("filled color now %s", filled_color_button.get_rgba ().to_string ()); + settings.set_string ("filled-color", filled_color_button.get_rgba ().to_string ()); + }); + + var main_box = new Gtk.Box (VERTICAL, 12) { margin_start = 12, margin_end = 12 }; + + var filled_color_preference = new PreferenceRow (_("Color of filled cells"), filled_color_button); + main_box.append (grade_preference); main_box.append (row_preference); main_box.append (column_preference); + main_box.append (filled_color_preference); + main_box.append (empty_color_preference); get_content_area ().append (main_box); add_button (_("Close"), Gtk.ResponseType.APPLY); From 2f1d063cb52bd27c537b4c105bd4f996f9b89402 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 11 Nov 2024 11:15:03 +0000 Subject: [PATCH 090/142] Tweak popover margins --- src/HeaderBar/AppPopover.vala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index ad0dbe8..b342dbb 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -23,7 +23,10 @@ public class Gnonograms.AppPopover : Gtk.Popover { var preferences_button = new PopoverButton (_("Preferences"), ACTION_PREFIX + ACTION_PREFERENCES); var solve_button = new PopoverButton (_("Solve"), ACTION_PREFIX + ACTION_SOLVE); - var settings_box = new Gtk.Box (VERTICAL, 3); + var settings_box = new Gtk.Box (VERTICAL, 3) { + margin_start = 12, + margin_end = 12, + }; settings_box.append (title_entry); settings_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); settings_box.append (load_game_button); From 97dcb66ab31970c2a9013b0e9939e5d9dd6f0cbc Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 11 Nov 2024 11:28:56 +0000 Subject: [PATCH 091/142] Bind game name to app_popover title entry text --- src/HeaderBar/AppPopover.vala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index b342dbb..1af1355 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -17,6 +17,7 @@ public class Gnonograms.AppPopover : Gtk.Popover { margin_top = 12, }; + controller.bind_property ("game-name", title_entry, "text", BIDIRECTIONAL | SYNC_CREATE); var load_game_button = new PopoverButton (_("Load"), ACTION_PREFIX + ACTION_OPEN); var save_game_button = new PopoverButton (_("Save"), ACTION_PREFIX + ACTION_SAVE); var save_as_game_button = new PopoverButton (_("Save to Different File"), ACTION_PREFIX + ACTION_SAVE_AS); From b0b3a309a77f2627c50dc6c0b747212aad72a4e1 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 11 Nov 2024 11:29:37 +0000 Subject: [PATCH 092/142] Cleanup --- src/HeaderBar/AppPopover.vala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 1af1355..dd8dc90 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -60,15 +60,11 @@ public class Gnonograms.AppPopover : Gtk.Popover { if (text != null && detailed_action != null) { var accels = ((Gtk.Application) Application.get_default ()).get_accels_for_action (detailed_action); if (accels != null) { - warning ("got accels"); child = new Granite.AccelLabel (text, accels[0]); return; - } else { - warning ("No accels for %s", detailed_action); - } + } } - warning ("fallback"); child = new Gtk.Label (text); } } From b91a724835c7754cabb0cf7695a4129b9ac14245 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 11 Nov 2024 11:35:06 +0000 Subject: [PATCH 093/142] Move PopoverButton to separate file --- meson.build | 1 + src/HeaderBar/AppPopover.vala | 28 --------------------------- src/HeaderBar/PopoverButton.vala | 33 ++++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 src/HeaderBar/PopoverButton.vala diff --git a/meson.build b/meson.build index fb355da..8e700ca 100644 --- a/meson.build +++ b/meson.build @@ -42,6 +42,7 @@ executable ( 'src/Controller.vala', 'src/View.vala', 'src/HeaderBar/HeaderButton.vala', + 'src/HeaderBar/PopoverButton.vala', 'src/HeaderBar/ProgressIndicator.vala', 'src/HeaderBar/RestartButton.vala', 'src/HeaderBar/AppPopover.vala', diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index dd8dc90..cc7f5ac 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -40,32 +40,4 @@ public class Gnonograms.AppPopover : Gtk.Popover { child = settings_box; } - - private class PopoverButton : Gtk.Button { - public string text { get; construct; } - public string detailed_action { get; construct; } - - public PopoverButton (string _text, string? _action_name = null) { - Object ( - text: _text, - detailed_action: _action_name // Assigning directly to Gtk.Button.action_name doesnt work for some reason - ); - } - - construct { - margin_top = 3; - margin_bottom = 3; - add_css_class (Granite.STYLE_CLASS_FLAT); - set_action_name (detailed_action); - if (text != null && detailed_action != null) { - var accels = ((Gtk.Application) Application.get_default ()).get_accels_for_action (detailed_action); - if (accels != null) { - child = new Granite.AccelLabel (text, accels[0]); - return; - } - } - - child = new Gtk.Label (text); - } - } } diff --git a/src/HeaderBar/PopoverButton.vala b/src/HeaderBar/PopoverButton.vala new file mode 100644 index 0000000..f9ddc95 --- /dev/null +++ b/src/HeaderBar/PopoverButton.vala @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten + * + * Authored by: Jeremy Wootten + */ +public class Gnonograms.PopoverButton : Gtk.Button { + public string text { get; construct; } + public string detailed_action { get; construct; } + + public PopoverButton (string _text, string? _action_name = null) { + Object ( + text: _text, + detailed_action: _action_name // Assigning directly to Gtk.Button.action_name doesnt work for some reason + ); + } + + construct { + margin_top = 3; + margin_bottom = 3; + add_css_class (Granite.STYLE_CLASS_FLAT); + set_action_name (detailed_action); + if (text != null && detailed_action != null) { + var accels = ((Gtk.Application) Application.get_default ()).get_accels_for_action (detailed_action); + if (accels != null) { + child = new Granite.AccelLabel (text, accels[0]); + return; + } + } + + child = new Gtk.Label (text); + } +} From 6dfeb90b96d461486d07150b7a38e1ce6f0576c7 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 11 Nov 2024 11:49:20 +0000 Subject: [PATCH 094/142] Move PreferenceDialog and PreferenceRow into HeaderBar folder --- meson.build | 3 +- src/HeaderBar/PreferenceRow.vala | 35 +++++++++++++++++++ .../PreferencesDialog.vala | 31 +--------------- src/View.vala | 2 +- 4 files changed, 39 insertions(+), 32 deletions(-) create mode 100644 src/HeaderBar/PreferenceRow.vala rename src/{Dialogs => HeaderBar}/PreferencesDialog.vala (81%) diff --git a/meson.build b/meson.build index 8e700ca..f843d9e 100644 --- a/meson.build +++ b/meson.build @@ -46,9 +46,10 @@ executable ( 'src/HeaderBar/ProgressIndicator.vala', 'src/HeaderBar/RestartButton.vala', 'src/HeaderBar/AppPopover.vala', + 'src/HeaderBar/PreferencesDialog.vala', + 'src/HeaderBar/PreferenceRow.vala', 'src/services/RandomPatternGenerator.vala', 'src/services/RandomGameGenerator.vala', - 'src/Dialogs/PreferencesDialog.vala', 'libcore/widgets/Cluebox.vala', 'libcore/widgets/Clue.vala', 'libcore/widgets/Cellgrid.vala', diff --git a/src/HeaderBar/PreferenceRow.vala b/src/HeaderBar/PreferenceRow.vala new file mode 100644 index 0000000..824fd13 --- /dev/null +++ b/src/HeaderBar/PreferenceRow.vala @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten + * + * Authored by: Jeremy Wootten + */ + +public class Gnonograms.PreferenceRow : Gtk.Box { + public string text { get; construct; } + public Gtk.Widget widget { get; construct; } + public PreferenceRow (string text, Gtk.Widget setting_widget) { + Object ( + text: text, + widget: setting_widget + ); + } + + construct { + orientation = Gtk.Orientation.HORIZONTAL; + margin_top = 3; + margin_bottom = 6; + spacing = 12; + hexpand = true; + + var label = new Gtk.Label (text) { + halign = Gtk.Align.START + }; + + widget.halign = Gtk.Align.END; + widget.hexpand = true; + + append (label); + append (widget); + } +} diff --git a/src/Dialogs/PreferencesDialog.vala b/src/HeaderBar/PreferencesDialog.vala similarity index 81% rename from src/Dialogs/PreferencesDialog.vala rename to src/HeaderBar/PreferencesDialog.vala index eb03b67..bb55153 100644 --- a/src/Dialogs/PreferencesDialog.vala +++ b/src/HeaderBar/PreferencesDialog.vala @@ -5,7 +5,7 @@ * Authored by: Jeremy Wootten */ -public class Gnonograms.Dialogs.Preferences : Granite.Dialog { +public class Gnonograms.PreferencesDialog : Granite.Dialog { construct { set_default_size (400, 100); resizable = false; @@ -95,33 +95,4 @@ public class Gnonograms.Dialogs.Preferences : Granite.Dialog { settings.set_enum ("grade", (Difficulty)(grade_setting.selected)); }); } - - private class PreferenceRow : Gtk.Box { - public string text { get; construct; } - public Gtk.Widget widget { get; construct; } - public PreferenceRow (string text, Gtk.Widget setting_widget) { - Object ( - text: text, - widget: setting_widget - ); - } - - construct { - orientation = Gtk.Orientation.HORIZONTAL; - margin_top = 3; - margin_bottom = 6; - spacing = 12; - hexpand = true; - - var label = new Gtk.Label (text) { - halign = Gtk.Align.START - }; - - widget.halign = Gtk.Align.END; - widget.hexpand = true; - - append (label); - append (widget); - } - } } diff --git a/src/View.vala b/src/View.vala index 9494ab0..7505e69 100644 --- a/src/View.vala +++ b/src/View.vala @@ -614,7 +614,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private void action_preferences () { app_popover.popdown (); - var dialog = new Dialogs.Preferences () { + var dialog = new PreferencesDialog () { transient_for = this, title = _("Preferences") }; From 1af9c2b04e85fb7516006ca837378ef2a43a9caf Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 11 Nov 2024 12:36:59 +0000 Subject: [PATCH 095/142] Add color style widgets to preference dialog --- src/HeaderBar/PreferencesDialog.vala | 32 ++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/HeaderBar/PreferencesDialog.vala b/src/HeaderBar/PreferencesDialog.vala index bb55153..1b4c47a 100644 --- a/src/HeaderBar/PreferencesDialog.vala +++ b/src/HeaderBar/PreferencesDialog.vala @@ -45,12 +45,10 @@ public class Gnonograms.PreferencesDialog : Granite.Dialog { var empty_color = settings.get_string ("empty-color"); var rgba = Gdk.RGBA (); if (rgba.parse (empty_color)) { - warning ("empty_colorr setting is %s", rgba.to_string ()); empty_color_button.set_rgba (rgba); } empty_color_button.notify["rgba"].connect (() => { - warning ("empty color now %s", empty_color_button.get_rgba ().to_string ()); settings.set_string ("empty-color", empty_color_button.get_rgba ().to_string ()); }); var empty_color_preference = new PreferenceRow (_("Color of empty cells"), empty_color_button); @@ -70,19 +68,41 @@ public class Gnonograms.PreferencesDialog : Granite.Dialog { settings.set_string ("filled-color", filled_color_button.get_rgba ().to_string ()); }); + var filled_color_preference = new PreferenceRow (_("Color of filled cells"), filled_color_button); - var main_box = new Gtk.Box (VERTICAL, 12) { - margin_start = 12, - margin_end = 12 + var follow_system_switchmodelbutton = new Granite.SwitchModelButton (_("Follow System Style")) { + margin_top = 3 }; - var filled_color_preference = new PreferenceRow (_("Color of filled cells"), filled_color_button); + var color_mode_switch = new Granite.ModeSwitch.from_icon_name ( + "weather-clear-symbolic", + "weather-clear-night-symbolic" + ) { + primary_icon_tooltip_text = _("Light"), + secondary_icon_tooltip_text = _("Dark") + }; + var color_mode_preference = new PreferenceRow (_("Color Style"), color_mode_switch) { + margin_start = margin_start + 12 + }; + var color_revealer = new Gtk.Revealer (); + color_revealer.set_child (color_mode_preference); + follow_system_switchmodelbutton.bind_property ( + "active", + color_revealer, "reveal-child", + INVERT_BOOLEAN | SYNC_CREATE + ); + + var main_box = new Gtk.Box (VERTICAL, 12) { + margin_start = 12 + }; main_box.append (grade_preference); main_box.append (row_preference); main_box.append (column_preference); main_box.append (filled_color_preference); main_box.append (empty_color_preference); + main_box.append (follow_system_switchmodelbutton); + main_box.append (color_revealer); get_content_area ().append (main_box); add_button (_("Close"), Gtk.ResponseType.APPLY); From 1893cbb9cd90fe165d2ea28b5a51e50890966c40 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 11 Nov 2024 13:29:08 +0000 Subject: [PATCH 096/142] Change color style according to widget settings --- ...com.github.jeremypw.gnonograms.gschema.xml | 17 ++++++ src/HeaderBar/PreferencesDialog.vala | 20 ++++++- src/View.vala | 55 ++++++++++++++++--- 3 files changed, 80 insertions(+), 12 deletions(-) diff --git a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml index f810434..a5424b0 100644 --- a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml +++ b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml @@ -68,6 +68,23 @@ Defaults to yellow + + + true + Whether to follow system appearance style + + Whether to follow system 'prefers-dark' style setting or to use + the application preference. + + + + + false + Prefer dark style + + Whether to use a dark style when not following the system style + + diff --git a/src/HeaderBar/PreferencesDialog.vala b/src/HeaderBar/PreferencesDialog.vala index 1b4c47a..feae021 100644 --- a/src/HeaderBar/PreferencesDialog.vala +++ b/src/HeaderBar/PreferencesDialog.vala @@ -75,7 +75,7 @@ public class Gnonograms.PreferencesDialog : Granite.Dialog { }; var color_mode_switch = new Granite.ModeSwitch.from_icon_name ( - "weather-clear-symbolic", + "weather-clear-symbolic", "weather-clear-night-symbolic" ) { primary_icon_tooltip_text = _("Light"), @@ -87,8 +87,8 @@ public class Gnonograms.PreferencesDialog : Granite.Dialog { var color_revealer = new Gtk.Revealer (); color_revealer.set_child (color_mode_preference); follow_system_switchmodelbutton.bind_property ( - "active", - color_revealer, "reveal-child", + "active", + color_revealer, "reveal-child", INVERT_BOOLEAN | SYNC_CREATE ); @@ -114,5 +114,19 @@ public class Gnonograms.PreferencesDialog : Granite.Dialog { grade_setting.notify["selected"].connect (() => { settings.set_enum ("grade", (Difficulty)(grade_setting.selected)); }); + + settings.bind ( + "follow-system-style", + follow_system_switchmodelbutton, + "active", + SettingsBindFlags.DEFAULT + ); + + settings.bind ( + "prefer-dark-style", + color_mode_switch, + "active", + SettingsBindFlags.DEFAULT + ); } } diff --git a/src/View.vala b/src/View.vala index 7505e69..760e555 100644 --- a/src/View.vala +++ b/src/View.vala @@ -10,6 +10,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private const string PAINT_FILL_ACCEL = "f"; // Must be lower case private const string PAINT_EMPTY_ACCEL = "e"; // Must be lower case private const string PAINT_UNKNOWN_ACCEL = "x"; // Must be lower case + private const uint DARK = Granite.Settings.ColorScheme.DARK; public static Gee.MultiMap action_accelerators; public static Gtk.Application app; @@ -133,15 +134,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { construct { title = _("Gnonograms"); set_default_size (900, 700); - var granite_settings = Granite.Settings.get_default (); - var gtk_settings = Gtk.Settings.get_default (); - var prefer_dark = granite_settings.prefers_color_scheme == DARK; - gtk_settings.gtk_application_prefer_dark_theme = prefer_dark; - granite_settings.notify["prefers-color-scheme"].connect (() => { - prefer_dark = granite_settings.prefers_color_scheme == DARK; - gtk_settings.gtk_application_prefer_dark_theme = prefer_dark; - }); - var view_actions = new GLib.SimpleActionGroup (); view_actions.add_action_entries (view_action_entries, this); insert_action_group (ACTION_GROUP, view_actions); @@ -384,6 +376,14 @@ public class Gnonograms.View : Gtk.ApplicationWindow { }); cell_grid.stop_drawing.connect (stop_painting); + + update_style (); + settings.changed["follow-system-style"].connect (() => { + update_style (); + }); + settings.changed["prefer-dark-style"].connect (() => { + update_style (); + }); } public string[] get_clues (bool is_column) { @@ -723,4 +723,41 @@ public class Gnonograms.View : Gtk.ApplicationWindow { make_move_at_cell (); } + + // Code based largely on elementary Code app + private ulong color_scheme_listener_handler_id = 0; + private void update_style () { + var gtk_settings = Gtk.Settings.get_default (); + var granite_settings = Granite.Settings.get_default (); + var following_system = settings.get_boolean ("follow-system-style"); + disconnect_color_scheme_preference_listener (following_system); + if (following_system) { + gtk_settings.gtk_application_prefer_dark_theme = ( + granite_settings.prefers_color_scheme == DARK + ); + color_scheme_listener_handler_id = granite_settings.notify["prefers-color-scheme"].connect (() => { + gtk_settings.gtk_application_prefer_dark_theme = ( + granite_settings.prefers_color_scheme == DARK + ); + }); + } else { + gtk_settings.gtk_application_prefer_dark_theme = settings.get_boolean ("prefer-dark-style"); + color_scheme_listener_handler_id = settings.notify["prefers-dark-style"].connect (() => { + gtk_settings.gtk_application_prefer_dark_theme = settings.get_boolean ("prefer-dark-style"); + }); + } + } + + private void disconnect_color_scheme_preference_listener (bool following_system) { + if (color_scheme_listener_handler_id != 0) { + if (following_system) { + var granite_settings = Granite.Settings.get_default (); + granite_settings.disconnect (color_scheme_listener_handler_id); + } else { + settings.disconnect (color_scheme_listener_handler_id); + } + + color_scheme_listener_handler_id = 0; + } + } } From 93870eeb35c882f13099c882a1373fe8347a9e07 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 11 Nov 2024 17:44:55 +0000 Subject: [PATCH 097/142] Discontinue clue help setting (always on) --- .../schemas/com.github.jeremypw.gnonograms.gschema.xml | 10 ---------- src/Controller.vala | 1 - src/View.vala | 7 +------ 3 files changed, 1 insertion(+), 17 deletions(-) diff --git a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml index a5424b0..f0e2292 100644 --- a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml +++ b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml @@ -41,16 +41,6 @@ - - true - Visual hints in clues - - Use strikethrough for each contiguous completed block from edge in clue label. - Mark clue in red if there is a definite error in the corresponding region. - Fade clue if corresponding region is completed without definite error. - - - "rgba(24,18,151,1.0)" Color of filled cell when solving diff --git a/src/Controller.vala b/src/Controller.vala index f869580..9636db3 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -95,7 +95,6 @@ public class Gnonograms.Controller : GLib.Object { saved_state.bind ("mode", this, "game-state", SettingsBindFlags.DEFAULT); saved_state.bind ("current-game-path", this, "current-game-path", SettingsBindFlags.DEFAULT); settings.bind ("grade", this, "generator-grade", SettingsBindFlags.DEFAULT); - settings.bind ("clue-help", view, "strikeout-complete", SettingsBindFlags.DEFAULT); settings.bind ("rows", this, "rows", SettingsBindFlags.DEFAULT); settings.bind ("columns", this, "columns", SettingsBindFlags.DEFAULT); diff --git a/src/View.vala b/src/View.vala index 760e555..2b219a4 100644 --- a/src/View.vala +++ b/src/View.vala @@ -49,7 +49,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public Difficulty generator_grade { get; set; } public Difficulty game_grade { get; set; default = Difficulty.UNDEFINED;} public string game_name { get { return controller.game_name; } } - public bool strikeout_complete { get; set; } public bool readonly { get; set; default = false;} public bool can_go_back { get; set; } public bool can_go_forward { get; set; } @@ -345,10 +344,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } }); - notify["strikeout-complete"].connect (() => { - update_all_labels_completeness (); - }); - cell_grid.leave.connect (() => { row_clue_box.unhighlight_all (); column_clue_box.unhighlight_all (); @@ -516,7 +511,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private void update_clue_complete (uint idx, bool is_col) { var lbox = is_col ? column_clue_box : row_clue_box; - if (controller.game_state == GameState.SOLVING && strikeout_complete) { + if (controller.game_state == GameState.SOLVING) { var blocks = Gee.List.empty (); blocks = model.get_complete_blocks_from_working (idx, is_col); lbox.update_clue_complete (idx, blocks); From a14008d21dfb0dbe00d16b8abdb156b37e80cd85 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 11 Nov 2024 17:54:09 +0000 Subject: [PATCH 098/142] Move CellPattern to separate file --- libcore/widgets/CellPattern.vala | 74 ++++++++++++++++++++++++++++++++ libcore/widgets/Cellgrid.vala | 69 ----------------------------- meson.build | 1 + 3 files changed, 75 insertions(+), 69 deletions(-) create mode 100644 libcore/widgets/CellPattern.vala diff --git a/libcore/widgets/CellPattern.vala b/libcore/widgets/CellPattern.vala new file mode 100644 index 0000000..80dd47e --- /dev/null +++ b/libcore/widgets/CellPattern.vala @@ -0,0 +1,74 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten + * + * Authored by: Jeremy Wootten + */ +public class CellPattern : GLib.Object { + public Cairo.Pattern pattern; + public double width { get; construct; } + public double height { get; construct; } + private double red; + private double green; + private double blue; + private double x0 = 0; + private double y0 = 0; + private Cairo.Matrix matrix; + + public CellPattern.cell (Gdk.RGBA color) { + red = color.red; + green = color.green; + blue = color.blue; + matrix = Cairo.Matrix.identity (); + + var granite_settings = Granite.Settings.get_default (); + set_pattern (granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK); + + granite_settings.notify["prefers-color-scheme"].connect (() => { + set_pattern (granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK); + }); + } + + public CellPattern.highlight (double wd, double ht) { + Object ( + width: wd, + height: ht + ); + } + + construct { + var r = double.min (width, height) / 2.0; + var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, (int)width , (int)height); + var context = new Cairo.Context (surface); + context.set_source_rgb (0.0, 0.0, 0.0); + context.rectangle (0, 0, width, height); + context.fill (); + context.arc (width / 2.0, height / 2.0, r - 2.0, 0, 2 * Math.PI); + context.set_source_rgba (1.0, 1.0, 1.0, 0.5); + context.set_operator (Cairo.Operator.SOURCE); + context.fill (); + + pattern = new Cairo.Pattern.for_surface (surface); + pattern.set_extend (Cairo.Extend.NONE); + matrix = Cairo.Matrix.identity (); + pattern.set_matrix (matrix); + } + + public void move_to (double x, double y) { + var xx = x - x0; + var yy = y - y0; + matrix.translate (-xx, -yy); + pattern.set_matrix (matrix); + x0 = x; + y0 = y; + } + + private void set_pattern (bool is_dark) { + pattern = new Cairo.Pattern.rgba ( + is_dark ? red / 2 : red, + is_dark ? green / 2 : green, + is_dark ? blue / 2 : blue, + 1.0 + ); + } +} diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 22cdc5f..b2dc13a 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -339,73 +339,4 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { leave (); return; } - - private class CellPattern : GLib.Object { - public Cairo.Pattern pattern; - public double width { get; construct; } - public double height { get; construct; } - private double red; - private double green; - private double blue; - private double x0 = 0; - private double y0 = 0; - private Cairo.Matrix matrix; - - public CellPattern.cell (Gdk.RGBA color) { - red = color.red; - green = color.green; - blue = color.blue; - matrix = Cairo.Matrix.identity (); - - var granite_settings = Granite.Settings.get_default (); - set_pattern (granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK); - - granite_settings.notify["prefers-color-scheme"].connect (() => { - set_pattern (granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK); - }); - } - - public CellPattern.highlight (double wd, double ht) { - Object ( - width: wd, - height: ht - ); - } - - construct { - var r = double.min (width, height) / 2.0; - var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, (int)width , (int)height); - var context = new Cairo.Context (surface); - context.set_source_rgb (0.0, 0.0, 0.0); - context.rectangle (0, 0, width, height); - context.fill (); - context.arc (width / 2.0, height / 2.0, r - 2.0, 0, 2 * Math.PI); - context.set_source_rgba (1.0, 1.0, 1.0, 0.5); - context.set_operator (Cairo.Operator.SOURCE); - context.fill (); - - pattern = new Cairo.Pattern.for_surface (surface); - pattern.set_extend (Cairo.Extend.NONE); - matrix = Cairo.Matrix.identity (); - pattern.set_matrix (matrix); - } - - public void move_to (double x, double y) { - var xx = x - x0; - var yy = y - y0; - matrix.translate (-xx, -yy); - pattern.set_matrix (matrix); - x0 = x; - y0 = y; - } - - private void set_pattern (bool is_dark) { - pattern = new Cairo.Pattern.rgba ( - is_dark ? red / 2 : red, - is_dark ? green / 2 : green, - is_dark ? blue / 2 : blue, - 1.0 - ); - } - } } diff --git a/meson.build b/meson.build index f843d9e..246042b 100644 --- a/meson.build +++ b/meson.build @@ -53,6 +53,7 @@ executable ( 'libcore/widgets/Cluebox.vala', 'libcore/widgets/Clue.vala', 'libcore/widgets/Cellgrid.vala', + 'libcore/widgets/CellPattern.vala', 'libcore/utils.vala', 'libcore/Model.vala', 'libcore/My2DCellArray.vala', From 876bf1cbced7232fb44381f639c004c1793d08e4 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 12 Nov 2024 15:44:01 +0000 Subject: [PATCH 099/142] Update cluebox size request add move clues; tweak to minimise window change --- libcore/widgets/Cellgrid.vala | 4 ++-- libcore/widgets/Cluebox.vala | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index b2dc13a..4ef0693 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -175,8 +175,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { cell_width = width / c; cell_height = height / r; - content_width = (int) width; - content_height = (int) height; + content_width = (int) (width + 0.99); + content_height = (int) (height + 0.99); /* Cause refresh of existing pattern */ highlight_pattern = new CellPattern.highlight (cell_width, cell_height); diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index 2f26acc..659e581 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -47,7 +47,10 @@ public class Gnonograms.ClueBox : Gtk.Widget { view.controller.notify ["rows"].connect (add_remove_clues); } - notify["cell-size"].connect (() => { + notify["cell-size"].connect (update_size_request); + } + + private void update_size_request () { font_desc.set_absolute_size (cell_size * PIX_TO_PANGO_FONT); var index = 0.0; var size = (int) cell_size; @@ -55,6 +58,7 @@ public class Gnonograms.ClueBox : Gtk.Widget { var shortfall = 0.0; // Assign label widths to match grid lines as closely as possible. // As the cell dimensions are non-integral we have to vary the (integral) label widths + var box_size = 0; foreach (Clue clue in clues) { var makeup = 0; if (shortfall >= 1.0) { @@ -65,18 +69,21 @@ public class Gnonograms.ClueBox : Gtk.Widget { var label = clue.label; if (holds_column_clues) { label.width_request = size + makeup; - label.height_request = (int) (cell_size * (double) n_cells / 3.0); + box_size += label.width_request; } else { label.height_request = size + makeup; - label.width_request = (int) (cell_size * (double) n_cells / 3.0); + box_size += label.height_request; } index++; shortfall += diff; } - }); - + if (holds_column_clues) { + set_size_request (box_size, (int) (cell_size / 2.0 * (n_cells / 3 + 2))); + } else { + set_size_request ((int) (cell_size / 2.0 * (n_cells / 3 + 2)), box_size); + } } private void add_remove_clues () { @@ -101,6 +108,8 @@ public class Gnonograms.ClueBox : Gtk.Widget { clue.label.set_parent (this); } } + + update_size_request (); } public string[] get_clue_texts () { From a5788423b91346fe347e1c008c2c5bacfb148b42 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 12 Nov 2024 16:50:32 +0000 Subject: [PATCH 100/142] Add actions and controls for window size --- src/Application.vala | 3 +++ src/HeaderBar/AppPopover.vala | 43 ++++++++++++++++++++++++++++++++++- src/View.vala | 31 +++++++++++++++++++++++-- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index 0a370c2..5f7b9c6 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -30,6 +30,9 @@ namespace Gnonograms { public const string ACTION_HINT = "action-hint"; public const string ACTION_OPTIONS = "action-options"; public const string ACTION_OPTIONS_ACCEL = ""; + public const string ACTION_ZOOM_SMALLER = "action-zoom-smaller"; + public const string ACTION_ZOOM_DEFAULT = "action-zoom-default"; + public const string ACTION_ZOOM_LARGER = "action-zoom-larger"; public const string ACTION_PREFERENCES = "action-preferences"; #if WITH_DEBUGGING diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index cc7f5ac..cd3eb35 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -12,12 +12,51 @@ public class Gnonograms.AppPopover : Gtk.Popover { ); } construct { + var app = (Gtk.Application)(GLib.Application.get_default ()); + var title_entry = new Gtk.Entry () { placeholder_text = _("Enter title of game here"), margin_top = 12, }; - controller.bind_property ("game-name", title_entry, "text", BIDIRECTIONAL | SYNC_CREATE); + + var zoom_smaller_button = new PopoverButton ("", ACTION_PREFIX + ACTION_ZOOM_SMALLER) { + icon_name = "zoom-out-symbolic" + }; + zoom_smaller_button.tooltip_markup = Granite.markup_accel_tooltip ( + app.get_accels_for_action (zoom_smaller_button.action_name), + _("Shrink Window") + ); + var zoom_default_button = new PopoverButton ("", ACTION_PREFIX + ACTION_ZOOM_DEFAULT) { + icon_name = "zoom-original-symbolic" + }; + zoom_default_button.tooltip_markup = Granite.markup_accel_tooltip ( + app.get_accels_for_action (zoom_default_button.action_name), + _("Default Window Size") + ); + var zoom_larger_button = new PopoverButton ("", ACTION_PREFIX + ACTION_ZOOM_LARGER) { + icon_name = "zoom-in-symbolic" + }; + zoom_larger_button.tooltip_markup = Granite.markup_accel_tooltip ( + app.get_accels_for_action (zoom_larger_button.action_name), + _("Expand Window") + ); + + var cell_size_button_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) { + homogeneous = true, + hexpand = true, + margin_end = 6, + margin_start = 6 + }; + cell_size_button_box.add_css_class (Granite.STYLE_CLASS_LINKED); + cell_size_button_box.append (zoom_smaller_button); + cell_size_button_box.append (zoom_default_button); + cell_size_button_box.append (zoom_larger_button); + + var cell_size_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12); + cell_size_box.append (new Gtk.Label (_("Window size"))); + cell_size_box.append (cell_size_button_box); + var load_game_button = new PopoverButton (_("Load"), ACTION_PREFIX + ACTION_OPEN); var save_game_button = new PopoverButton (_("Save"), ACTION_PREFIX + ACTION_SAVE); var save_as_game_button = new PopoverButton (_("Save to Different File"), ACTION_PREFIX + ACTION_SAVE_AS); @@ -30,6 +69,8 @@ public class Gnonograms.AppPopover : Gtk.Popover { }; settings_box.append (title_entry); settings_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); + settings_box.append (cell_size_box); + settings_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); settings_box.append (load_game_button); settings_box.append (save_game_button); settings_box.append (save_as_game_button); diff --git a/src/View.vala b/src/View.vala index 2b219a4..32b1591 100644 --- a/src/View.vala +++ b/src/View.vala @@ -7,6 +7,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private const uint PROGRESS_DELAY_MSEC = 500; + private const int DEFAULT_WIDTH = 900; + private const int DEFAULT_HEIGHT = 700; private const string PAINT_FILL_ACCEL = "f"; // Must be lower case private const string PAINT_EMPTY_ACCEL = "e"; // Must be lower case private const string PAINT_UNKNOWN_ACCEL = "x"; // Must be lower case @@ -35,7 +37,10 @@ public class Gnonograms.View : Gtk.ApplicationWindow { {ACTION_SOLVE, action_solve}, {ACTION_HINT, action_hint}, {ACTION_OPTIONS, action_options}, - {ACTION_PREFERENCES, action_preferences} + {ACTION_PREFERENCES, action_preferences}, + {ACTION_ZOOM_SMALLER, action_zoom_smaller}, + {ACTION_ZOOM_DEFAULT, action_zoom_default}, + {ACTION_ZOOM_LARGER, action_zoom_larger} }; #if WITH_DEBUGGING @@ -123,6 +128,10 @@ public class Gnonograms.View : Gtk.ApplicationWindow { action_accelerators.set (ACTION_OPTIONS, "F10"); action_accelerators.set (ACTION_OPTIONS, "Menu"); action_accelerators.set (ACTION_PREFERENCES, "P"); + action_accelerators.set (ACTION_ZOOM_LARGER, "plus"); + action_accelerators.set (ACTION_ZOOM_LARGER, "equal"); + action_accelerators.set (ACTION_ZOOM_DEFAULT, "0"); + action_accelerators.set (ACTION_ZOOM_SMALLER, "minus"); #if WITH_DEBUGGING action_accelerators.set (ACTION_DEBUG_ROW, "R"); action_accelerators.set (ACTION_DEBUG_COL, "C"); @@ -132,7 +141,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { construct { title = _("Gnonograms"); - set_default_size (900, 700); + set_default_size (DEFAULT_WIDTH, DEFAULT_HEIGHT); var view_actions = new GLib.SimpleActionGroup (); view_actions.add_action_entries (view_action_entries, this); insert_action_group (ACTION_GROUP, view_actions); @@ -650,6 +659,24 @@ public class Gnonograms.View : Gtk.ApplicationWindow { controller.save_game_as.begin (); } + private void action_zoom_larger () { + var current_width = this.default_width; + this.default_width = current_width + current_width / 10; + var current_height = this.default_height; + this.default_height = current_height + current_height / 10; + } + + private void action_zoom_smaller () { + var current_width = this.default_width; + this.default_width = current_width - current_width / 10; + var current_height = this.default_height; + this.default_height = current_height - current_height / 10; + } + + private void action_zoom_default () { + this.default_width = DEFAULT_WIDTH; + this.default_height = DEFAULT_HEIGHT; + } private void action_check_errors () { if (controller.rewind_until_correct () == 0) { From b1bef0047772735ddc2c68f1789c0331b33749ff Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 13 Nov 2024 18:08:49 +0000 Subject: [PATCH 101/142] Tweak .editorconfig --- .editorconfig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.editorconfig b/.editorconfig index bd78bcb..cbf86a3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,3 +10,8 @@ insert_final_newline = true max_line_length = 80 tab_width = 4 +[*.ui] +tab_width = 2 + +[*.xml] +tab_width = 2 From dbd74f25e51590602b5395861918dd799b57df3b Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 13 Nov 2024 18:11:07 +0000 Subject: [PATCH 102/142] Implement shortcut window --- data/gresource.xml | 1 - meson.build | 2 + src/Application.vala | 1 + src/HeaderBar/AppPopover.vala | 4 + src/ShortcutWindow.vala | 10 ++ src/View.vala | 9 +- src/services/ShortcutHelper.vala | 19 ++++ src/ui/shortcuthelper.ui.vala | 160 +++++++++++++++++++++++++++++++ 8 files changed, 204 insertions(+), 2 deletions(-) delete mode 100644 data/gresource.xml create mode 100644 src/ShortcutWindow.vala create mode 100644 src/services/ShortcutHelper.vala create mode 100644 src/ui/shortcuthelper.ui.vala diff --git a/data/gresource.xml b/data/gresource.xml deleted file mode 100644 index c532425..0000000 --- a/data/gresource.xml +++ /dev/null @@ -1 +0,0 @@ -# Thinking head icon not required for Gtk4 version diff --git a/meson.build b/meson.build index 246042b..7924725 100644 --- a/meson.build +++ b/meson.build @@ -50,6 +50,8 @@ executable ( 'src/HeaderBar/PreferenceRow.vala', 'src/services/RandomPatternGenerator.vala', 'src/services/RandomGameGenerator.vala', + 'src/services/ShortcutHelper.vala', + 'src/ui/shortcuthelper.ui.vala', 'libcore/widgets/Cluebox.vala', 'libcore/widgets/Clue.vala', 'libcore/widgets/Cellgrid.vala', diff --git a/src/Application.vala b/src/Application.vala index 5f7b9c6..caf47a8 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -33,6 +33,7 @@ namespace Gnonograms { public const string ACTION_ZOOM_SMALLER = "action-zoom-smaller"; public const string ACTION_ZOOM_DEFAULT = "action-zoom-default"; public const string ACTION_ZOOM_LARGER = "action-zoom-larger"; + public const string ACTION_SHORTCUT_WINDOW = "action-shortcut-window"; public const string ACTION_PREFERENCES = "action-preferences"; #if WITH_DEBUGGING diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index cd3eb35..4e955da 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -63,6 +63,8 @@ public class Gnonograms.AppPopover : Gtk.Popover { var preferences_button = new PopoverButton (_("Preferences"), ACTION_PREFIX + ACTION_PREFERENCES); var solve_button = new PopoverButton (_("Solve"), ACTION_PREFIX + ACTION_SOLVE); + var shortcut_button = new PopoverButton (_("Keyboard Shortcuts"), ACTION_PREFIX + ACTION_SHORTCUT_WINDOW); + var settings_box = new Gtk.Box (VERTICAL, 3) { margin_start = 12, margin_end = 12, @@ -78,6 +80,8 @@ public class Gnonograms.AppPopover : Gtk.Popover { settings_box.append (solve_button); settings_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); settings_box.append (preferences_button); + settings_box.append (shortcut_button); + // settings_box.append (about_button); child = settings_box; } diff --git a/src/ShortcutWindow.vala b/src/ShortcutWindow.vala new file mode 100644 index 0000000..69b6a10 --- /dev/null +++ b/src/ShortcutWindow.vala @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten + * + * Authored by: Jeremy Wootten + */ + + public class ShortcutHelper : Object { + + } diff --git a/src/View.vala b/src/View.vala index 32b1591..fcd7eb6 100644 --- a/src/View.vala +++ b/src/View.vala @@ -40,7 +40,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { {ACTION_PREFERENCES, action_preferences}, {ACTION_ZOOM_SMALLER, action_zoom_smaller}, {ACTION_ZOOM_DEFAULT, action_zoom_default}, - {ACTION_ZOOM_LARGER, action_zoom_larger} + {ACTION_ZOOM_LARGER, action_zoom_larger}, + {ACTION_SHORTCUT_WINDOW, action_shortcut_window} }; #if WITH_DEBUGGING @@ -135,6 +136,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { #if WITH_DEBUGGING action_accelerators.set (ACTION_DEBUG_ROW, "R"); action_accelerators.set (ACTION_DEBUG_COL, "C"); + action_accelerators.set (ACTION_SHORTCUT_WINDOW, "F1"); #endif } @@ -678,6 +680,11 @@ public class Gnonograms.View : Gtk.ApplicationWindow { this.default_height = DEFAULT_HEIGHT; } + private void action_shortcut_window () { + var helper = new ShortcutHelper (); + helper.show_window (); + } + private void action_check_errors () { if (controller.rewind_until_correct () == 0) { send_notification (_("No errors")); diff --git a/src/services/ShortcutHelper.vala b/src/services/ShortcutHelper.vala new file mode 100644 index 0000000..cb592af --- /dev/null +++ b/src/services/ShortcutHelper.vala @@ -0,0 +1,19 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten + * + * Authored by: Jeremy Wootten + */ + + public class Gnonograms.ShortcutHelper : Object { + private Gtk.ShortcutsWindow window; + + construct { + var builder = new Gtk.Builder.from_string (shortcuthelper_ui, -1); + window = (Gtk.ShortcutsWindow) builder.get_object ("shortcuts-window"); + } + + public void show_window () { + window.present (); + } + } diff --git a/src/ui/shortcuthelper.ui.vala b/src/ui/shortcuthelper.ui.vala new file mode 100644 index 0000000..08d4008 --- /dev/null +++ b/src/ui/shortcuthelper.ui.vala @@ -0,0 +1,160 @@ +namespace Gnonograms { + const string shortcuthelper_ui = """ + + + 1 + + + Keyboard Shortcuts + 15 + + + Drawing + + + F + Paint FILLED + + + + + E + Paint EMPTY + + + + + X + Paint UNKNOWN + + + + + Left Right Up Down + Move cursor + + + + + + + Game + + + <Ctrl>N <Ctrl>3 + Generate new game + + + + + <Ctrl>H F9 + Hint + + + + + <Ctrl>R F5 + Restart current game + + + + + <Ctrl>Z + Undo last move + + + + + <Shift><Ctrl>Z + Redo last move + + + + + F7 + Check for and remove errors + + + + + <Ctrl>H F9 + Hint + + + + + <Alt>S + Solve by computer + + + + + + + Mode + + + <Ctrl>1 + Designing mode + + + + + <Ctrl>2 + Solving mode + + + + + + + General + + + Menu + Show App Menu + + + + + <Ctrl>P + Show Preferences Dialog + + + + + <Ctrl>K + Show Keyboard Shortcuts + + + + + + + Files + + + <Ctrl>O + Load game from a .gno file + + + + + <Ctrl>S + Save game to a .gno file + + + + + <Ctrl>S + Save game to a diffent file + + + + + + + + +"""; +} From 901964cdfa050cde741e6eca73bd75c1e4bfaca6 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 15 Nov 2024 16:45:56 +0000 Subject: [PATCH 103/142] Add additional shortcut to Shortcut winddow --- src/Application.vala | 1 - src/View.vala | 4 +++- src/ui/shortcuthelper.ui.vala | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index caf47a8..878b3e9 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -34,7 +34,6 @@ namespace Gnonograms { public const string ACTION_ZOOM_DEFAULT = "action-zoom-default"; public const string ACTION_ZOOM_LARGER = "action-zoom-larger"; public const string ACTION_SHORTCUT_WINDOW = "action-shortcut-window"; - public const string ACTION_PREFERENCES = "action-preferences"; #if WITH_DEBUGGING public const string ACTION_DEBUG_ROW = "action-debug-row"; diff --git a/src/View.vala b/src/View.vala index fcd7eb6..fb3fc20 100644 --- a/src/View.vala +++ b/src/View.vala @@ -129,6 +129,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { action_accelerators.set (ACTION_OPTIONS, "F10"); action_accelerators.set (ACTION_OPTIONS, "Menu"); action_accelerators.set (ACTION_PREFERENCES, "P"); + action_accelerators.set (ACTION_SHORTCUT_WINDOW, "K"); + action_accelerators.set (ACTION_SHORTCUT_WINDOW, "F1"); action_accelerators.set (ACTION_ZOOM_LARGER, "plus"); action_accelerators.set (ACTION_ZOOM_LARGER, "equal"); action_accelerators.set (ACTION_ZOOM_DEFAULT, "0"); @@ -136,7 +138,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { #if WITH_DEBUGGING action_accelerators.set (ACTION_DEBUG_ROW, "R"); action_accelerators.set (ACTION_DEBUG_COL, "C"); - action_accelerators.set (ACTION_SHORTCUT_WINDOW, "F1"); #endif } @@ -681,6 +682,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } private void action_shortcut_window () { +warning ("action shortcut window"); var helper = new ShortcutHelper (); helper.show_window (); } diff --git a/src/ui/shortcuthelper.ui.vala b/src/ui/shortcuthelper.ui.vala index 08d4008..7751710 100644 --- a/src/ui/shortcuthelper.ui.vala +++ b/src/ui/shortcuthelper.ui.vala @@ -123,7 +123,7 @@ namespace Gnonograms { - <Ctrl>K + <Ctrl>K F1 Show Keyboard Shortcuts From e21ae7a865a3aa0e149a12b9e5a583f026b916e5 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 15 Nov 2024 18:16:43 +0000 Subject: [PATCH 104/142] Bump version to 4.0.0, add skeletion release note; add min gtk4 version --- data/com.github.jeremypw.gnonograms.appdata.xml.in | 7 +++++++ meson.build | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/data/com.github.jeremypw.gnonograms.appdata.xml.in b/data/com.github.jeremypw.gnonograms.appdata.xml.in index 7c46086..a0a9f0f 100644 --- a/data/com.github.jeremypw.gnonograms.appdata.xml.in +++ b/data/com.github.jeremypw.gnonograms.appdata.xml.in @@ -38,6 +38,13 @@ + + +
    +
  • Port to Gtk-4
  • +
+
+
    diff --git a/meson.build b/meson.build index 7924725..7c39ced 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project ( 'com.github.jeremypw.gnonograms', 'vala', 'c', - version: '2.1.2', + version: '4.0.0', meson_version: '>= 0.58.0' ) @@ -30,7 +30,7 @@ config_file = configure_file( gnonogram_deps = [ dependency('glib-2.0'), dependency('gobject-2.0'), - dependency('gtk4'), + dependency('gtk4', version: '>=4.10'), dependency('gee-0.8', version: '>=0.8.5'), dependency('granite-7'), dependency('libadwaita-1') From b5593fc0fafe8f2a350d4cc1b49f4a6477208653 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 15 Nov 2024 18:18:09 +0000 Subject: [PATCH 105/142] Add About button to AppMenu and show About Dialog --- src/Application.vala | 2 ++ src/HeaderBar/AppPopover.vala | 3 ++- src/View.vala | 21 +++++++++++++++++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index 878b3e9..b5fdb26 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -34,7 +34,9 @@ namespace Gnonograms { public const string ACTION_ZOOM_DEFAULT = "action-zoom-default"; public const string ACTION_ZOOM_LARGER = "action-zoom-larger"; public const string ACTION_SHORTCUT_WINDOW = "action-shortcut-window"; + public const string ACTION_ABOUT_WINDOW = "action-about-dialog"; public const string ACTION_PREFERENCES = "action-preferences"; + #if WITH_DEBUGGING public const string ACTION_DEBUG_ROW = "action-debug-row"; public const string ACTION_DEBUG_COL = "action-debug-col"; diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 4e955da..17c1a3e 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -64,6 +64,7 @@ public class Gnonograms.AppPopover : Gtk.Popover { var solve_button = new PopoverButton (_("Solve"), ACTION_PREFIX + ACTION_SOLVE); var shortcut_button = new PopoverButton (_("Keyboard Shortcuts"), ACTION_PREFIX + ACTION_SHORTCUT_WINDOW); + var about_button = new PopoverButton (_("About Gnonograms"), ACTION_PREFIX + ACTION_ABOUT_WINDOW); var settings_box = new Gtk.Box (VERTICAL, 3) { margin_start = 12, @@ -81,7 +82,7 @@ public class Gnonograms.AppPopover : Gtk.Popover { settings_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); settings_box.append (preferences_button); settings_box.append (shortcut_button); - // settings_box.append (about_button); + settings_box.append (about_button); child = settings_box; } diff --git a/src/View.vala b/src/View.vala index fb3fc20..a7880ed 100644 --- a/src/View.vala +++ b/src/View.vala @@ -41,7 +41,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { {ACTION_ZOOM_SMALLER, action_zoom_smaller}, {ACTION_ZOOM_DEFAULT, action_zoom_default}, {ACTION_ZOOM_LARGER, action_zoom_larger}, - {ACTION_SHORTCUT_WINDOW, action_shortcut_window} + {ACTION_SHORTCUT_WINDOW, action_shortcut_window}, + {ACTION_ABOUT_WINDOW, action_about_dialog} }; #if WITH_DEBUGGING @@ -682,11 +683,27 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } private void action_shortcut_window () { -warning ("action shortcut window"); var helper = new ShortcutHelper (); helper.show_window (); } + private void action_about_dialog () { + Gtk.show_about_dialog ( + this, + "authors", new string[1] {"Jeremy Wootten"}, + "comments", _("An implementation of the Japanese logic puzzle \"Nonograms\" written in Vala, allowing the user to solve computer generated puzzles or design their own."), + "copyright", _("2010-2024 Jeremy Wootten"), + "license_type", Gtk.License.LGPL_2_1, + "logo_icon_name", "com.github.jeremypw.gnonograms", + "program_name", _("Gnonograms"), + "translator_credits", "NathanBnm (French)\n André Barata (Portuguese)\n Heimen Stoffels (Dutch)", + "version", "4.0.0", + "website", "https://github.com/jeremypw/gnonograms", + "website_label", "Source Code", + null + ); + } + private void action_check_errors () { if (controller.rewind_until_correct () == 0) { send_notification (_("No errors")); From ceebcf71b75e3d238c39319f3960512ee999fe4f Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Fri, 15 Nov 2024 18:21:31 +0000 Subject: [PATCH 106/142] Fix deprecated meson option --- meson_options.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson_options.txt b/meson_options.txt index ecc3110..0a1258d 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1 +1 @@ -option('with_debugging', type : 'boolean', value : 'false', description : 'Include code used for debugging the solver') +option('with_debugging', type : 'boolean', value : false, description : 'Include code used for debugging the solver') From e3f9d6f01324b251e077287d2ab08d27dace5654 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 8 Dec 2024 13:22:26 +0000 Subject: [PATCH 107/142] Fix keypress paint --- src/Application.vala | 3 --- src/View.vala | 62 +++++++++++++++++++++++++++----------------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index b5fdb26..f9491d4 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -21,9 +21,6 @@ namespace Gnonograms { public const string ACTION_OPEN = "action-open"; public const string ACTION_SAVE = "action-save"; public const string ACTION_SAVE_AS = "action-save-as"; - public const string ACTION_PAINT_FILLED = "action-paint-filled"; - public const string ACTION_PAINT_EMPTY = "action-paint-empty"; - public const string ACTION_PAINT_UNKNOWN = "action-paint-unknown"; public const string ACTION_CHECK_ERRORS = "action-check-errors"; public const string ACTION_RESTART = "action-restart"; public const string ACTION_SOLVE = "action-solve"; diff --git a/src/View.vala b/src/View.vala index a7880ed..1e1ec6b 100644 --- a/src/View.vala +++ b/src/View.vala @@ -9,9 +9,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private const uint PROGRESS_DELAY_MSEC = 500; private const int DEFAULT_WIDTH = 900; private const int DEFAULT_HEIGHT = 700; - private const string PAINT_FILL_ACCEL = "f"; // Must be lower case - private const string PAINT_EMPTY_ACCEL = "e"; // Must be lower case - private const string PAINT_UNKNOWN_ACCEL = "x"; // Must be lower case private const uint DARK = Granite.Settings.ColorScheme.DARK; public static Gee.MultiMap action_accelerators; @@ -29,9 +26,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { {ACTION_OPEN, action_open}, {ACTION_SAVE, action_save}, {ACTION_SAVE_AS, action_save_as}, - {ACTION_PAINT_FILLED, action_paint_filled}, - {ACTION_PAINT_EMPTY, action_paint_empty}, - {ACTION_PAINT_UNKNOWN, action_paint_unknown}, {ACTION_CHECK_ERRORS, action_check_errors}, {ACTION_RESTART, action_restart}, {ACTION_SOLVE, action_solve}, @@ -83,6 +77,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { // private Gtk.Button auto_solve_button; private Gtk.Button restart_button; private uint drawing_with_key; + private uint paint_fill_key = Gdk.keyval_from_name ("f"); + private uint paint_empty_key = Gdk.keyval_from_name ("e"); + private uint paint_unknown_key = Gdk.keyval_from_name ("x"); public View (Model _model, Controller controller) { Object ( @@ -118,9 +115,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { action_accelerators.set (ACTION_OPEN, "O"); action_accelerators.set (ACTION_SAVE, "S"); action_accelerators.set (ACTION_SAVE_AS, "S"); - action_accelerators.set (ACTION_PAINT_FILLED, PAINT_FILL_ACCEL); - action_accelerators.set (ACTION_PAINT_EMPTY, PAINT_EMPTY_ACCEL); - action_accelerators.set (ACTION_PAINT_UNKNOWN, PAINT_UNKNOWN_ACCEL); action_accelerators.set (ACTION_CHECK_ERRORS, "F7"); action_accelerators.set (ACTION_RESTART, "F5"); action_accelerators.set (ACTION_RESTART, "R"); @@ -277,12 +271,26 @@ public class Gnonograms.View : Gtk.ApplicationWindow { var key_controller = new Gtk.EventControllerKey (); main_grid.add_controller (key_controller); + key_controller.key_pressed.connect ((keyval, keycode, state) => { + warning ("key pressed"); + if (keyval == paint_fill_key) { + paint_filled (); + } else if (keyval == paint_empty_key) { + paint_empty (); + } else if (keyval == paint_unknown_key) { + paint_unknown (); + } else { + return false;; + } + + return true; + }); + key_controller.key_released.connect ((keyval, keycode, state) => { - if (Gdk.keyval_to_lower (keyval) == drawing_with_key) { + warning ("key released"); + if (keyval == drawing_with_key) { stop_painting (); } - - return; }); toast_overlay = new Adw.ToastOverlay () { @@ -352,7 +360,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { highlight_labels (previous_cell, false); highlight_labels (current_cell, true); - if (current_cell != NULL_CELL && drawing_with_state != CellState.UNDEFINED) { + if (current_cell != NULL_CELL && + drawing_with_state != CellState.UNDEFINED) { + make_move_at_cell (); } }); @@ -728,12 +738,15 @@ public class Gnonograms.View : Gtk.ApplicationWindow { return; } - Cell target = {current_cell.row + row_delta, - current_cell.col + col_delta, - CellState.UNDEFINED - }; + Cell target = { + current_cell.row + row_delta, + current_cell.col + col_delta, + CellState.UNDEFINED + }; + + if (target.row >= controller.dimensions.height || + target.col >= controller.dimensions.width) { - if (target.row >= controller.dimensions.height || target.col >= controller.dimensions.width) { return; } @@ -750,17 +763,18 @@ public class Gnonograms.View : Gtk.ApplicationWindow { controller.game_state = GameState.GENERATING; } - private void action_paint_filled () { + private void paint_filled () { paint_cell_state (CellState.FILLED); - drawing_with_key = Gdk.keyval_from_name (PAINT_FILL_ACCEL); + drawing_with_key = paint_fill_key; } - private void action_paint_empty () { + private void paint_empty () { paint_cell_state (CellState.EMPTY); - drawing_with_key = Gdk.keyval_from_name (PAINT_EMPTY_ACCEL); + drawing_with_key = paint_empty_key; } - private void action_paint_unknown () { + + private void paint_unknown () { paint_cell_state (CellState.UNKNOWN); - drawing_with_key = Gdk.keyval_from_name (PAINT_UNKNOWN_ACCEL); + drawing_with_key = paint_unknown_key; } private void paint_cell_state (CellState cs) { if (cs == CellState.UNKNOWN && controller.game_state != GameState.SOLVING) { From 4852612995687ec334deecbd8864607568ea74d7 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 8 Dec 2024 15:36:55 +0000 Subject: [PATCH 108/142] Move button controller into View --- libcore/Enums.vala | 7 ------ libcore/widgets/Cellgrid.vala | 15 ------------ src/Application.vala | 7 ++++++ src/Controller.vala | 5 ++++ src/View.vala | 44 +++++++++++++++++------------------ 5 files changed, 33 insertions(+), 45 deletions(-) diff --git a/libcore/Enums.vala b/libcore/Enums.vala index 6e7d10e..3ae4c6e 100644 --- a/libcore/Enums.vala +++ b/libcore/Enums.vala @@ -51,13 +51,6 @@ namespace Gnonograms { } } - public enum GameState { - SETTING, - SOLVING, - GENERATING, - UNDEFINED = 99; - } - public enum CellState { UNKNOWN, EMPTY, diff --git a/libcore/widgets/Cellgrid.vala b/libcore/widgets/Cellgrid.vala index 4ef0693..6cfefc8 100644 --- a/libcore/widgets/Cellgrid.vala +++ b/libcore/widgets/Cellgrid.vala @@ -5,8 +5,6 @@ * Authored by: Jeremy Wootten */ public class Gnonograms.CellGrid : Gtk.DrawingArea { - public signal void start_drawing (uint button, Gdk.ModifierType state, bool double_click); - public signal void stop_drawing (); public signal void leave (); public unowned View view { get; construct; } @@ -86,19 +84,6 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { motion_controller.motion.connect (on_pointer_moved); motion_controller.leave.connect (on_leave_notify); - var button_controller = new Gtk.GestureClick (); - button_controller.set_button (0); // Listen to any button - add_controller (button_controller); - button_controller.pressed.connect ((n_press, x, y) => { - start_drawing ( - button_controller.get_current_button (), - button_controller.get_current_event_state (), - n_press > 1); - }); - button_controller.released.connect ((n_press, x, y) => { - stop_drawing (); - }); - set_draw_func (draw_func); notify["current-cell"].connect (() => { diff --git a/src/Application.vala b/src/Application.vala index f9491d4..bb60bf9 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -5,6 +5,13 @@ * Authored by: Jeremy Wootten */ namespace Gnonograms { + public enum GameState { + SETTING, + SOLVING, + GENERATING, + UNDEFINED = 99; + } + public const string ACTION_GROUP = "win"; public const string ACTION_PREFIX = ACTION_GROUP + "."; public const string ACTION_UNDO = "action-undo"; diff --git a/src/Controller.vala b/src/Controller.vala index 9636db3..d476181 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -15,6 +15,11 @@ public class Gnonograms.Controller : GLib.Object { return {columns, rows}; } } + + public bool is_solving { + get { return game_state == SOLVING; } + } + public uint rows { get; set;} public uint columns { get; set; } diff --git a/src/View.vala b/src/View.vala index 1e1ec6b..2434d1f 100644 --- a/src/View.vala +++ b/src/View.vala @@ -293,6 +293,27 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } }); + var button_controller = new Gtk.GestureClick (); + button_controller.set_button (0); // Listen to any button + main_grid.add_controller (button_controller); + button_controller.pressed.connect ((n_press, x, y) => { + var button = button_controller.get_current_button (); + var shift = (SHIFT_MASK in button_controller.get_current_event_state ()); + var set_unknown = (n_press == 2 || button == Gdk.BUTTON_MIDDLE); + var set_empty = (button == Gdk.BUTTON_SECONDARY || button == Gdk.BUTTON_PRIMARY && shift); + + if (set_unknown) { // Clear current cell + drawing_with_state = controller.is_solving ? CellState.UNKNOWN : CellState.EMPTY; + } else { // Paint current cell + drawing_with_state = set_empty ? CellState.EMPTY : CellState.FILLED; + } + + make_move_at_cell (); + }); + button_controller.released.connect ((n_press, x, y) => { + stop_painting (); + }); + toast_overlay = new Adw.ToastOverlay () { child = main_grid }; @@ -372,29 +393,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { column_clue_box.unhighlight_all (); }); - cell_grid.start_drawing.connect ((button, state, double_click) => { - if (double_click || button == Gdk.BUTTON_MIDDLE) { - if (controller.game_state == SOLVING) { - drawing_with_state = CellState.UNKNOWN; - } else { - drawing_with_state = CellState.EMPTY; - } - } else { - var shift = (state & Gdk.ModifierType.SHIFT_MASK) > 0; - if (button == Gdk.BUTTON_SECONDARY || - button == Gdk.BUTTON_PRIMARY && shift) { - - drawing_with_state = CellState.EMPTY; - } else { - drawing_with_state = CellState.FILLED; - } - } - - make_move_at_cell (); - }); - - cell_grid.stop_drawing.connect (stop_painting); - update_style (); settings.changed["follow-system-style"].connect (() => { update_style (); From e6fba83652ec3449bae07198798346d1158b746d Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 8 Dec 2024 15:48:08 +0000 Subject: [PATCH 109/142] Cleanup --- libcore/Model.vala | 6 +++--- libcore/widgets/Cluebox.vala | 2 +- src/HeaderBar/PopoverButton.vala | 2 +- src/ShortcutWindow.vala | 4 ++-- src/View.vala | 2 +- src/services/ShortcutHelper.vala | 4 ++-- src/ui/shortcuthelper.ui.vala | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/libcore/Model.vala b/libcore/Model.vala index ac0d8a8..d6f8562 100644 --- a/libcore/Model.vala +++ b/libcore/Model.vala @@ -23,12 +23,12 @@ public class Gnonograms.Model : GLib.Object { private My2DCellArray solution_data { get; set; } private My2DCellArray working_data { get; set; } - private uint rows { + private uint rows { get { return controller.rows; } } - private uint cols { + private uint cols { get { return controller.columns; } @@ -53,7 +53,7 @@ public class Gnonograms.Model : GLib.Object { make_data_arrays (); changed (); } - + private void make_data_arrays () { solution_data = new My2DCellArray (controller.dimensions, CellState.EMPTY); working_data = new My2DCellArray (controller.dimensions, CellState.UNKNOWN); diff --git a/libcore/widgets/Cluebox.vala b/libcore/widgets/Cluebox.vala index 659e581..b307ee1 100644 --- a/libcore/widgets/Cluebox.vala +++ b/libcore/widgets/Cluebox.vala @@ -68,7 +68,7 @@ public class Gnonograms.ClueBox : Gtk.Widget { var label = clue.label; if (holds_column_clues) { - label.width_request = size + makeup; + label.width_request = size + makeup; box_size += label.width_request; } else { label.height_request = size + makeup; diff --git a/src/HeaderBar/PopoverButton.vala b/src/HeaderBar/PopoverButton.vala index f9ddc95..195f2b6 100644 --- a/src/HeaderBar/PopoverButton.vala +++ b/src/HeaderBar/PopoverButton.vala @@ -25,7 +25,7 @@ public class Gnonograms.PopoverButton : Gtk.Button { if (accels != null) { child = new Granite.AccelLabel (text, accels[0]); return; - } + } } child = new Gtk.Label (text); diff --git a/src/ShortcutWindow.vala b/src/ShortcutWindow.vala index 69b6a10..4e59e05 100644 --- a/src/ShortcutWindow.vala +++ b/src/ShortcutWindow.vala @@ -4,7 +4,7 @@ * * Authored by: Jeremy Wootten */ - + public class ShortcutHelper : Object { - + } diff --git a/src/View.vala b/src/View.vala index 2434d1f..2fd12a1 100644 --- a/src/View.vala +++ b/src/View.vala @@ -280,7 +280,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } else if (keyval == paint_unknown_key) { paint_unknown (); } else { - return false;; + return false; } return true; diff --git a/src/services/ShortcutHelper.vala b/src/services/ShortcutHelper.vala index cb592af..d6a854f 100644 --- a/src/services/ShortcutHelper.vala +++ b/src/services/ShortcutHelper.vala @@ -9,10 +9,10 @@ private Gtk.ShortcutsWindow window; construct { - var builder = new Gtk.Builder.from_string (shortcuthelper_ui, -1); + var builder = new Gtk.Builder.from_string (SHORTCUT_HELPER_UI, -1); window = (Gtk.ShortcutsWindow) builder.get_object ("shortcuts-window"); } - + public void show_window () { window.present (); } diff --git a/src/ui/shortcuthelper.ui.vala b/src/ui/shortcuthelper.ui.vala index 7751710..d63910a 100644 --- a/src/ui/shortcuthelper.ui.vala +++ b/src/ui/shortcuthelper.ui.vala @@ -1,5 +1,5 @@ namespace Gnonograms { - const string shortcuthelper_ui = """ + const string SHORTCUT_HELPER_UI = """ 1 From 2ea37f2472adad24ad14640a028b73f2a2db0666 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 8 Dec 2024 15:51:02 +0000 Subject: [PATCH 110/142] Lose unused window --- src/ShortcutWindow.vala | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 src/ShortcutWindow.vala diff --git a/src/ShortcutWindow.vala b/src/ShortcutWindow.vala deleted file mode 100644 index 4e59e05..0000000 --- a/src/ShortcutWindow.vala +++ /dev/null @@ -1,10 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten - * - * Authored by: Jeremy Wootten - */ - - public class ShortcutHelper : Object { - - } From 312353542fcaf110f2a5278df6c332ecb02c63cb Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 8 Dec 2024 16:25:21 +0000 Subject: [PATCH 111/142] Restructure source folders --- src/Config.vala.in => Config.vala.in | 0 meson.build | 45 +++++++++++-------- {libcore => src}/Model.vala | 0 .../PreferencesDialog.vala | 0 {libcore => src/misc}/Constants.vala | 0 {libcore => src/misc}/Enums.vala | 0 {libcore => src/misc}/Structs.vala | 0 {libcore => src/misc}/utils.vala | 0 {libcore => src/objects}/Move.vala | 0 {libcore => src/objects}/My2DCellArray.vala | 0 {libcore => src/objects}/Region.vala | 0 {libcore => src/services}/Filereader.vala | 0 {libcore => src/services}/Filewriter.vala | 0 {libcore => src/services}/History.vala | 0 {libcore => src/services}/Solver.vala | 0 {libcore => src}/widgets/CellPattern.vala | 0 {libcore => src}/widgets/Cellgrid.vala | 0 {libcore => src}/widgets/Clue.vala | 0 {libcore => src}/widgets/Cluebox.vala | 0 19 files changed, 26 insertions(+), 19 deletions(-) rename src/Config.vala.in => Config.vala.in (100%) rename {libcore => src}/Model.vala (100%) rename src/{HeaderBar => dialogs}/PreferencesDialog.vala (100%) rename {libcore => src/misc}/Constants.vala (100%) rename {libcore => src/misc}/Enums.vala (100%) rename {libcore => src/misc}/Structs.vala (100%) rename {libcore => src/misc}/utils.vala (100%) rename {libcore => src/objects}/Move.vala (100%) rename {libcore => src/objects}/My2DCellArray.vala (100%) rename {libcore => src/objects}/Region.vala (100%) rename {libcore => src/services}/Filereader.vala (100%) rename {libcore => src/services}/Filewriter.vala (100%) rename {libcore => src/services}/History.vala (100%) rename {libcore => src/services}/Solver.vala (100%) rename {libcore => src}/widgets/CellPattern.vala (100%) rename {libcore => src}/widgets/Cellgrid.vala (100%) rename {libcore => src}/widgets/Clue.vala (100%) rename {libcore => src}/widgets/Cluebox.vala (100%) diff --git a/src/Config.vala.in b/Config.vala.in similarity index 100% rename from src/Config.vala.in rename to Config.vala.in diff --git a/meson.build b/meson.build index 7c39ced..8d274fc 100644 --- a/meson.build +++ b/meson.build @@ -22,7 +22,7 @@ config_data.set_quoted('GETTEXT_PACKAGE', meson.project_name()) config_data.set_quoted('VERSION', meson.project_version()) config_data.set_quoted('APP_ID', meson.project_name()) config_file = configure_file( - input: 'src/Config.vala.in', + input: 'Config.vala.in', output: '@BASENAME@', configuration: config_data ) @@ -41,33 +41,40 @@ executable ( 'src/Application.vala', 'src/Controller.vala', 'src/View.vala', + 'src/Model.vala', + 'src/HeaderBar/HeaderButton.vala', 'src/HeaderBar/PopoverButton.vala', 'src/HeaderBar/ProgressIndicator.vala', 'src/HeaderBar/RestartButton.vala', 'src/HeaderBar/AppPopover.vala', - 'src/HeaderBar/PreferencesDialog.vala', 'src/HeaderBar/PreferenceRow.vala', + + 'src/dialogs/PreferencesDialog.vala', + + 'src/ui/shortcuthelper.ui.vala', + + 'src/widgets/Cluebox.vala', + 'src/widgets/Clue.vala', + 'src/widgets/Cellgrid.vala', + 'src/widgets/CellPattern.vala', + + 'src/objects/My2DCellArray.vala', + 'src/objects/Region.vala', + 'src/objects/Move.vala', + 'src/services/RandomPatternGenerator.vala', 'src/services/RandomGameGenerator.vala', 'src/services/ShortcutHelper.vala', - 'src/ui/shortcuthelper.ui.vala', - 'libcore/widgets/Cluebox.vala', - 'libcore/widgets/Clue.vala', - 'libcore/widgets/Cellgrid.vala', - 'libcore/widgets/CellPattern.vala', - 'libcore/utils.vala', - 'libcore/Model.vala', - 'libcore/My2DCellArray.vala', - 'libcore/Region.vala', - 'libcore/Solver.vala', - 'libcore/Filereader.vala', - 'libcore/Filewriter.vala', - 'libcore/Move.vala', - 'libcore/History.vala', - 'libcore/Enums.vala', - 'libcore/Structs.vala', - 'libcore/Constants.vala', + 'src/services/Solver.vala', + 'src/services/Filereader.vala', + 'src/services/Filewriter.vala', + 'src/services/History.vala', + + 'src/misc/utils.vala', + 'src/misc/Enums.vala', + 'src/misc/Structs.vala', + 'src/misc/Constants.vala', config_file, dependencies : gnonogram_deps, install: true diff --git a/libcore/Model.vala b/src/Model.vala similarity index 100% rename from libcore/Model.vala rename to src/Model.vala diff --git a/src/HeaderBar/PreferencesDialog.vala b/src/dialogs/PreferencesDialog.vala similarity index 100% rename from src/HeaderBar/PreferencesDialog.vala rename to src/dialogs/PreferencesDialog.vala diff --git a/libcore/Constants.vala b/src/misc/Constants.vala similarity index 100% rename from libcore/Constants.vala rename to src/misc/Constants.vala diff --git a/libcore/Enums.vala b/src/misc/Enums.vala similarity index 100% rename from libcore/Enums.vala rename to src/misc/Enums.vala diff --git a/libcore/Structs.vala b/src/misc/Structs.vala similarity index 100% rename from libcore/Structs.vala rename to src/misc/Structs.vala diff --git a/libcore/utils.vala b/src/misc/utils.vala similarity index 100% rename from libcore/utils.vala rename to src/misc/utils.vala diff --git a/libcore/Move.vala b/src/objects/Move.vala similarity index 100% rename from libcore/Move.vala rename to src/objects/Move.vala diff --git a/libcore/My2DCellArray.vala b/src/objects/My2DCellArray.vala similarity index 100% rename from libcore/My2DCellArray.vala rename to src/objects/My2DCellArray.vala diff --git a/libcore/Region.vala b/src/objects/Region.vala similarity index 100% rename from libcore/Region.vala rename to src/objects/Region.vala diff --git a/libcore/Filereader.vala b/src/services/Filereader.vala similarity index 100% rename from libcore/Filereader.vala rename to src/services/Filereader.vala diff --git a/libcore/Filewriter.vala b/src/services/Filewriter.vala similarity index 100% rename from libcore/Filewriter.vala rename to src/services/Filewriter.vala diff --git a/libcore/History.vala b/src/services/History.vala similarity index 100% rename from libcore/History.vala rename to src/services/History.vala diff --git a/libcore/Solver.vala b/src/services/Solver.vala similarity index 100% rename from libcore/Solver.vala rename to src/services/Solver.vala diff --git a/libcore/widgets/CellPattern.vala b/src/widgets/CellPattern.vala similarity index 100% rename from libcore/widgets/CellPattern.vala rename to src/widgets/CellPattern.vala diff --git a/libcore/widgets/Cellgrid.vala b/src/widgets/Cellgrid.vala similarity index 100% rename from libcore/widgets/Cellgrid.vala rename to src/widgets/Cellgrid.vala diff --git a/libcore/widgets/Clue.vala b/src/widgets/Clue.vala similarity index 100% rename from libcore/widgets/Clue.vala rename to src/widgets/Clue.vala diff --git a/libcore/widgets/Cluebox.vala b/src/widgets/Cluebox.vala similarity index 100% rename from libcore/widgets/Cluebox.vala rename to src/widgets/Cluebox.vala From 3c53e63e4bc5957dffe16020dfbe5f533171fc4b Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 8 Dec 2024 16:33:41 +0000 Subject: [PATCH 112/142] Lose window size controls in app menu --- src/HeaderBar/AppPopover.vala | 39 ----------------------------------- 1 file changed, 39 deletions(-) diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 17c1a3e..0fc0e58 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -20,43 +20,6 @@ public class Gnonograms.AppPopover : Gtk.Popover { }; controller.bind_property ("game-name", title_entry, "text", BIDIRECTIONAL | SYNC_CREATE); - var zoom_smaller_button = new PopoverButton ("", ACTION_PREFIX + ACTION_ZOOM_SMALLER) { - icon_name = "zoom-out-symbolic" - }; - zoom_smaller_button.tooltip_markup = Granite.markup_accel_tooltip ( - app.get_accels_for_action (zoom_smaller_button.action_name), - _("Shrink Window") - ); - var zoom_default_button = new PopoverButton ("", ACTION_PREFIX + ACTION_ZOOM_DEFAULT) { - icon_name = "zoom-original-symbolic" - }; - zoom_default_button.tooltip_markup = Granite.markup_accel_tooltip ( - app.get_accels_for_action (zoom_default_button.action_name), - _("Default Window Size") - ); - var zoom_larger_button = new PopoverButton ("", ACTION_PREFIX + ACTION_ZOOM_LARGER) { - icon_name = "zoom-in-symbolic" - }; - zoom_larger_button.tooltip_markup = Granite.markup_accel_tooltip ( - app.get_accels_for_action (zoom_larger_button.action_name), - _("Expand Window") - ); - - var cell_size_button_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) { - homogeneous = true, - hexpand = true, - margin_end = 6, - margin_start = 6 - }; - cell_size_button_box.add_css_class (Granite.STYLE_CLASS_LINKED); - cell_size_button_box.append (zoom_smaller_button); - cell_size_button_box.append (zoom_default_button); - cell_size_button_box.append (zoom_larger_button); - - var cell_size_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12); - cell_size_box.append (new Gtk.Label (_("Window size"))); - cell_size_box.append (cell_size_button_box); - var load_game_button = new PopoverButton (_("Load"), ACTION_PREFIX + ACTION_OPEN); var save_game_button = new PopoverButton (_("Save"), ACTION_PREFIX + ACTION_SAVE); var save_as_game_button = new PopoverButton (_("Save to Different File"), ACTION_PREFIX + ACTION_SAVE_AS); @@ -72,8 +35,6 @@ public class Gnonograms.AppPopover : Gtk.Popover { }; settings_box.append (title_entry); settings_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); - settings_box.append (cell_size_box); - settings_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); settings_box.append (load_game_button); settings_box.append (save_game_button); settings_box.append (save_as_game_button); From cf37feb645c39d7cf35bb7b11f7f5e1b268d6458 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 8 Dec 2024 16:47:04 +0000 Subject: [PATCH 113/142] Reinstate auto-solve button --- src/View.vala | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/View.vala b/src/View.vala index 2fd12a1..5071d77 100644 --- a/src/View.vala +++ b/src/View.vala @@ -74,7 +74,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private Gtk.Button check_correct_button; private Gtk.Button hint_button; private AppPopover app_popover; - // private Gtk.Button auto_solve_button; + private Gtk.Button auto_solve_button; private Gtk.Button restart_button; private uint drawing_with_key; private uint paint_fill_key = Gdk.keyval_from_name ("f"); @@ -179,11 +179,11 @@ public class Gnonograms.View : Gtk.ApplicationWindow { ACTION_PREFIX + ACTION_HINT, _("Suggest next move") ); - // auto_solve_button = new HeaderButton ( - // "system", - // ACTION_PREFIX + ACTION_SOLVE, - // _("Solve by Computer") - // ); + auto_solve_button = new HeaderButton ( + "computer-symbolic", + ACTION_PREFIX + ACTION_SOLVE, + _("Solve by Computer") + ); generate_button = new HeaderButton ( "list-add", ACTION_PREFIX + ACTION_GENERATING_MODE, @@ -243,6 +243,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { header_bar.pack_start (check_correct_button); header_bar.pack_end (menu_button); header_bar.pack_end (mode_switch); + header_bar.pack_end (auto_solve_button); set_titlebar (header_bar); @@ -510,7 +511,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { can_go_back; hint_button.sensitive = sensitive && controller.game_state == GameState.SOLVING; - // auto_solve_button.sensitive = sensitive; + auto_solve_button.sensitive = controller.game_state == GameState.SETTING; } private void highlight_labels (Cell c, bool is_highlight) { From 6547057107c843a1fa004d6b9aceecc7c11c9428 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 8 Dec 2024 16:50:27 +0000 Subject: [PATCH 114/142] Use more symbolic icons --- src/View.vala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/View.vala b/src/View.vala index 5071d77..90f118f 100644 --- a/src/View.vala +++ b/src/View.vala @@ -167,7 +167,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { _("Check for Errors") ); restart_button = new RestartButton ( - "view-refresh", + "view-refresh-symbolic", ACTION_PREFIX + ACTION_RESTART, _("Start again") ) { @@ -175,7 +175,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { margin_start = 12, }; hint_button = new HeaderButton ( - "help-contents", + "help-contents-symbolic", ACTION_PREFIX + ACTION_HINT, _("Suggest next move") ); From 05860c24bc551e0b105c8c61b8fc493592ecd342 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 8 Dec 2024 16:57:52 +0000 Subject: [PATCH 115/142] Silence terminal warning re progress stack --- src/View.vala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/View.vala b/src/View.vala index 90f118f..ef11739 100644 --- a/src/View.vala +++ b/src/View.vala @@ -228,6 +228,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { }; progress_stack.add_named (progress_indicator, "Progress"); progress_stack.add_named (title_label, "Title"); + progress_stack.add_named (new Gtk.Label (""), "None"); progress_stack.set_visible_child_name ("Title"); header_bar = new Gtk.HeaderBar () { From 123303ba38cea8e7a69653a393e818c4ca154fa3 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 8 Dec 2024 17:13:04 +0000 Subject: [PATCH 116/142] Use autosolve only in designing mode --- src/Application.vala | 2 +- src/Controller.vala | 12 ------------ src/HeaderBar/AppPopover.vala | 4 ---- src/View.vala | 8 ++++---- 4 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index bb60bf9..4102451 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -30,7 +30,7 @@ namespace Gnonograms { public const string ACTION_SAVE_AS = "action-save-as"; public const string ACTION_CHECK_ERRORS = "action-check-errors"; public const string ACTION_RESTART = "action-restart"; - public const string ACTION_SOLVE = "action-solve"; + public const string ACTION_COMPUTER_SOLVE = "action-solve"; public const string ACTION_HINT = "action-hint"; public const string ACTION_OPTIONS = "action-options"; public const string ACTION_OPTIONS_ACCEL = ""; diff --git a/src/Controller.vala b/src/Controller.vala index d476181..1a44595 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -532,7 +532,6 @@ public class Gnonograms.Controller : GLib.Object { } public void computer_solve () { - game_state = GameState.SOLVING; start_solving.begin (true); } @@ -600,17 +599,6 @@ public class Gnonograms.Controller : GLib.Object { } view.game_grade = diff; - if (solver.state.solved ()) { - game_state = GameState.SOLVING; - if (copy_to_solution) { - model.copy_to_solution_data (solver.grid); - } - } - - if (copy_to_working) { - model.copy_to_working_data (solver.grid); - } - view.end_working (); return state; } diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 0fc0e58..1f92717 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -24,8 +24,6 @@ public class Gnonograms.AppPopover : Gtk.Popover { var save_game_button = new PopoverButton (_("Save"), ACTION_PREFIX + ACTION_SAVE); var save_as_game_button = new PopoverButton (_("Save to Different File"), ACTION_PREFIX + ACTION_SAVE_AS); var preferences_button = new PopoverButton (_("Preferences"), ACTION_PREFIX + ACTION_PREFERENCES); - var solve_button = new PopoverButton (_("Solve"), ACTION_PREFIX + ACTION_SOLVE); - var shortcut_button = new PopoverButton (_("Keyboard Shortcuts"), ACTION_PREFIX + ACTION_SHORTCUT_WINDOW); var about_button = new PopoverButton (_("About Gnonograms"), ACTION_PREFIX + ACTION_ABOUT_WINDOW); @@ -39,8 +37,6 @@ public class Gnonograms.AppPopover : Gtk.Popover { settings_box.append (save_game_button); settings_box.append (save_as_game_button); settings_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); - settings_box.append (solve_button); - settings_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); settings_box.append (preferences_button); settings_box.append (shortcut_button); settings_box.append (about_button); diff --git a/src/View.vala b/src/View.vala index ef11739..3c05929 100644 --- a/src/View.vala +++ b/src/View.vala @@ -28,7 +28,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { {ACTION_SAVE_AS, action_save_as}, {ACTION_CHECK_ERRORS, action_check_errors}, {ACTION_RESTART, action_restart}, - {ACTION_SOLVE, action_solve}, + {ACTION_COMPUTER_SOLVE, action_computer_solve}, {ACTION_HINT, action_hint}, {ACTION_OPTIONS, action_options}, {ACTION_PREFERENCES, action_preferences}, @@ -120,7 +120,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { action_accelerators.set (ACTION_RESTART, "R"); action_accelerators.set (ACTION_HINT, "F9"); action_accelerators.set (ACTION_HINT, "H"); - action_accelerators.set (ACTION_SOLVE, "S"); + action_accelerators.set (ACTION_COMPUTER_SOLVE, "S"); action_accelerators.set (ACTION_OPTIONS, "F10"); action_accelerators.set (ACTION_OPTIONS, "Menu"); action_accelerators.set (ACTION_PREFERENCES, "P"); @@ -181,7 +181,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { ); auto_solve_button = new HeaderButton ( "computer-symbolic", - ACTION_PREFIX + ACTION_SOLVE, + ACTION_PREFIX + ACTION_COMPUTER_SOLVE, _("Solve by Computer") ); generate_button = new HeaderButton ( @@ -618,7 +618,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } } - private void action_solve () { + private void action_computer_solve () requires (controller.game_state == GameState.SETTING) { controller.computer_solve (); } From 081920bee01331aadc21550bb4cc1b686d92e4f3 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 8 Dec 2024 17:15:23 +0000 Subject: [PATCH 117/142] Tweak auto solve tooltip --- src/View.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/View.vala b/src/View.vala index 3c05929..09de203 100644 --- a/src/View.vala +++ b/src/View.vala @@ -182,7 +182,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { auto_solve_button = new HeaderButton ( "computer-symbolic", ACTION_PREFIX + ACTION_COMPUTER_SOLVE, - _("Solve by Computer") + _("Check whether design is solvable") ); generate_button = new HeaderButton ( "list-add", From 4d6e07cdb57077e15cd493046fa5d211739034d4 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 8 Dec 2024 18:14:41 +0000 Subject: [PATCH 118/142] Move game size/difficulty controls into app popover --- src/HeaderBar/AppPopover.vala | 40 +++++++++++++++++ src/dialogs/PreferencesDialog.vala | 72 +++++++++++++++--------------- 2 files changed, 76 insertions(+), 36 deletions(-) diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 1f92717..2df7103 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -20,6 +20,35 @@ public class Gnonograms.AppPopover : Gtk.Popover { }; controller.bind_property ("game-name", title_entry, "text", BIDIRECTIONAL | SYNC_CREATE); + var grade_setting = new Gtk.DropDown.from_strings ( Difficulty.all_human ()); + var grade_preference = new PreferenceRow (_("Degree of difficulty"), grade_setting); + + var row_setting = new Gtk.SpinButton ( + new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), + 5.0, + 0 + ) { + snap_to_ticks = true, + orientation = Gtk.Orientation.HORIZONTAL, + width_chars = 3, + }; + + var row_preference = new PreferenceRow (_("Rows"), row_setting); + + var column_setting = new Gtk.SpinButton ( + new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), + 5.0, + 0 + ) { + snap_to_ticks = true, + orientation = Gtk.Orientation.HORIZONTAL, + width_chars = 3 + }; + + var column_preference = new PreferenceRow (_("Columns"), column_setting); + //TODO Add Clue help switch + + var load_game_button = new PopoverButton (_("Load"), ACTION_PREFIX + ACTION_OPEN); var save_game_button = new PopoverButton (_("Save"), ACTION_PREFIX + ACTION_SAVE); var save_as_game_button = new PopoverButton (_("Save to Different File"), ACTION_PREFIX + ACTION_SAVE_AS); @@ -32,6 +61,9 @@ public class Gnonograms.AppPopover : Gtk.Popover { margin_end = 12, }; settings_box.append (title_entry); + settings_box.append (grade_preference); + settings_box.append (row_preference); + settings_box.append (column_preference); settings_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); settings_box.append (load_game_button); settings_box.append (save_game_button); @@ -42,5 +74,13 @@ public class Gnonograms.AppPopover : Gtk.Popover { settings_box.append (about_button); child = settings_box; + + settings.bind ("columns", column_setting, "value", DEFAULT); + settings.bind ("rows", row_setting, "value", DEFAULT); + + grade_setting.selected = settings.get_enum ("grade"); + grade_setting.notify["selected"].connect (() => { + settings.set_enum ("grade", (Difficulty)(grade_setting.selected)); + }); } } diff --git a/src/dialogs/PreferencesDialog.vala b/src/dialogs/PreferencesDialog.vala index feae021..4c1592d 100644 --- a/src/dialogs/PreferencesDialog.vala +++ b/src/dialogs/PreferencesDialog.vala @@ -9,33 +9,33 @@ public class Gnonograms.PreferencesDialog : Granite.Dialog { construct { set_default_size (400, 100); resizable = false; - var grade_setting = new Gtk.DropDown.from_strings ( Difficulty.all_human ()); - var grade_preference = new PreferenceRow (_("Degree of difficulty"), grade_setting); - - var row_setting = new Gtk.SpinButton ( - new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), - 5.0, - 0 - ) { - snap_to_ticks = true, - orientation = Gtk.Orientation.HORIZONTAL, - width_chars = 3, - }; - - var row_preference = new PreferenceRow (_("Rows"), row_setting); - - var column_setting = new Gtk.SpinButton ( - new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), - 5.0, - 0 - ) { - snap_to_ticks = true, - orientation = Gtk.Orientation.HORIZONTAL, - width_chars = 3 - }; - - var column_preference = new PreferenceRow (_("Columns"), column_setting); - //TODO Add Clue help switch + // var grade_setting = new Gtk.DropDown.from_strings ( Difficulty.all_human ()); + // var grade_preference = new PreferenceRow (_("Degree of difficulty"), grade_setting); + + // var row_setting = new Gtk.SpinButton ( + // new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), + // 5.0, + // 0 + // ) { + // snap_to_ticks = true, + // orientation = Gtk.Orientation.HORIZONTAL, + // width_chars = 3, + // }; + + // var row_preference = new PreferenceRow (_("Rows"), row_setting); + + // var column_setting = new Gtk.SpinButton ( + // new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), + // 5.0, + // 0 + // ) { + // snap_to_ticks = true, + // orientation = Gtk.Orientation.HORIZONTAL, + // width_chars = 3 + // }; + + // var column_preference = new PreferenceRow (_("Columns"), column_setting); + // //TODO Add Clue help switch var empty_color_dialog = new Gtk.ColorDialog () { title = _("Filled Color"), @@ -96,9 +96,9 @@ public class Gnonograms.PreferencesDialog : Granite.Dialog { margin_start = 12 }; - main_box.append (grade_preference); - main_box.append (row_preference); - main_box.append (column_preference); + // main_box.append (grade_preference); + // main_box.append (row_preference); + // main_box.append (column_preference); main_box.append (filled_color_preference); main_box.append (empty_color_preference); main_box.append (follow_system_switchmodelbutton); @@ -107,13 +107,13 @@ public class Gnonograms.PreferencesDialog : Granite.Dialog { get_content_area ().append (main_box); add_button (_("Close"), Gtk.ResponseType.APPLY); - settings.bind ("columns", column_setting, "value", DEFAULT); - settings.bind ("rows", row_setting, "value", DEFAULT); + // settings.bind ("columns", column_setting, "value", DEFAULT); + // settings.bind ("rows", row_setting, "value", DEFAULT); - grade_setting.selected = settings.get_enum ("grade"); - grade_setting.notify["selected"].connect (() => { - settings.set_enum ("grade", (Difficulty)(grade_setting.selected)); - }); + // grade_setting.selected = settings.get_enum ("grade"); + // grade_setting.notify["selected"].connect (() => { + // settings.set_enum ("grade", (Difficulty)(grade_setting.selected)); + // }); settings.bind ( "follow-system-style", From 560aea9e2184382159adeff21f494f8a20f8787f Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 8 Dec 2024 18:15:03 +0000 Subject: [PATCH 119/142] Start reducing back references --- src/Controller.vala | 21 +++++++++++---------- src/Model.vala | 29 ++++++++++++++--------------- src/View.vala | 10 ++++++++-- src/widgets/Cellgrid.vala | 21 ++++++++++++++------- src/widgets/Cluebox.vala | 14 ++++++-------- 5 files changed, 53 insertions(+), 42 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index 1a44595..2c5212d 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -20,7 +20,7 @@ public class Gnonograms.Controller : GLib.Object { get { return game_state == SOLVING; } } - public uint rows { get; set;} + public uint rows { get; set; } public uint columns { get; set; } public Difficulty generator_grade { get; set; } @@ -59,15 +59,8 @@ public class Gnonograms.Controller : GLib.Object { } }); - notify["columns"].connect (() => { - solver = new Solver (dimensions); - game_name = _(UNTITLED_NAME); - }); - - notify["rows"].connect (() => { - solver = new Solver (dimensions); - game_name = _(UNTITLED_NAME); - }); + notify["columns"].connect (on_dimensions_changed); + notify["rows"].connect (on_dimensions_changed); notify["current_game_path"].connect (() => { view.update_title (); @@ -148,6 +141,14 @@ public class Gnonograms.Controller : GLib.Object { }); } + private void on_dimensions_changed () { + solver = new Solver (dimensions); + game_name = _(UNTITLED_NAME); + + model.on_dimensions_changed (rows, columns); + view.on_dimensions_changed (rows, columns); + } + private void new_or_random_game () { if (game_state == GameState.SOLVING && game_name == null) { on_new_random_request (); diff --git a/src/Model.vala b/src/Model.vala index d6f8562..d8c53b1 100644 --- a/src/Model.vala +++ b/src/Model.vala @@ -23,14 +23,11 @@ public class Gnonograms.Model : GLib.Object { private My2DCellArray solution_data { get; set; } private My2DCellArray working_data { get; set; } - private uint rows { + private uint rows = 5; + private uint cols = 5; + private Dimensions dimensions { get { - return controller.rows; - } - } - private uint cols { - get { - return controller.columns; + return { cols, rows }; } } @@ -42,21 +39,23 @@ public class Gnonograms.Model : GLib.Object { construct { make_data_arrays (); - controller.notify["rows"].connect (on_changed_dimensions); - controller.notify["columns"].connect (on_changed_dimensions); + // controller.notify["rows"].connect (on_changed_dimensions); + // controller.notify["columns"].connect (on_changed_dimensions); controller.notify["game-state"].connect (() => { changed (); }); } - private void on_changed_dimensions () { + public void on_dimensions_changed (uint rows, uint cols) { + this.rows = rows; + this.cols = cols; make_data_arrays (); - changed (); + // changed (); } private void make_data_arrays () { - solution_data = new My2DCellArray (controller.dimensions, CellState.EMPTY); - working_data = new My2DCellArray (controller.dimensions, CellState.UNKNOWN); + solution_data = new My2DCellArray (dimensions, CellState.EMPTY); + working_data = new My2DCellArray (dimensions, CellState.UNKNOWN); } public int count_errors () { @@ -243,13 +242,13 @@ public class Gnonograms.Model : GLib.Object { } public My2DCellArray copy_working_data () { - var grid = new My2DCellArray (controller.dimensions, CellState.UNKNOWN); + var grid = new My2DCellArray (dimensions, CellState.UNKNOWN); grid.copy (working_data); return grid; } public My2DCellArray copy_solution_data () { - var grid = new My2DCellArray (controller.dimensions, CellState.UNKNOWN); + var grid = new My2DCellArray (dimensions, CellState.UNKNOWN); grid.copy (solution_data); return grid; } diff --git a/src/View.vala b/src/View.vala index 09de203..73ff5a2 100644 --- a/src/View.vala +++ b/src/View.vala @@ -249,8 +249,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { set_titlebar (header_bar); - row_clue_box = new ClueBox (this, false); - column_clue_box = new ClueBox (this, true); + row_clue_box = new ClueBox (false); + column_clue_box = new ClueBox (true); cell_grid = new CellGrid (this); cell_grid.bind_property ("cell-width", column_clue_box, "cell-size"); @@ -404,6 +404,12 @@ public class Gnonograms.View : Gtk.ApplicationWindow { }); } + public void on_dimensions_changed (uint rows, uint cols) { + row_clue_box.on_dimensions_changed (rows, cols); + column_clue_box.on_dimensions_changed (rows, cols); + cell_grid.on_dimensions_changed (rows, cols); + } + public string[] get_clues (bool is_column) { var label_box = is_column ? column_clue_box : row_clue_box; return label_box.get_clue_texts (); diff --git a/src/widgets/Cellgrid.vala b/src/widgets/Cellgrid.vala index 6cfefc8..020add9 100644 --- a/src/widgets/Cellgrid.vala +++ b/src/widgets/Cellgrid.vala @@ -47,7 +47,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { public double cell_height { get; private set; }/* Width and Height of cell including frame */ private bool dirty = false; /* Whether a redraw is needed */ - + private uint rows = 5; + private uint cols = 5; private Gdk.RGBA grid_color; private Gdk.RGBA fill_color; private Gdk.RGBA empty_color; @@ -103,8 +104,14 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { settings.changed["filled-color"].connect (set_colors); settings.changed["empty-color"].connect (set_colors); - view.controller.notify["rows"].connect (queue_allocate); - view.controller.notify["columns"].connect (queue_allocate); + // view.controller.notify["rows"].connect (queue_allocate); + // view.controller.notify["columns"].connect (queue_allocate); + } + + public void on_dimensions_changed (uint rows, uint cols) { + this.rows = rows; + this.cols = cols; + queue_allocate (); } public void set_colors () { @@ -133,8 +140,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } public override void size_allocate (int w, int h, int bl) { - var r = (double) view.controller.rows; - var c = (double) view.controller.columns; + var r = (double) rows; + var c = (double) cols; // Need to allow window to be shrunk and create bottom/end margins var dw = (double) w - c - 12; var dh = (double) h - r - 12; @@ -213,8 +220,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { cr.set_antialias (Cairo.Antialias.NONE); cr.set_line_width (MINOR_GRID_LINE_WIDTH); - var r = view.controller.rows; - var c = view.controller.columns; + var r = rows; + var c = cols; var w = cell_width; var h = cell_height; // Draw minor grid lines diff --git a/src/widgets/Cluebox.vala b/src/widgets/Cluebox.vala index b307ee1..f990575 100644 --- a/src/widgets/Cluebox.vala +++ b/src/widgets/Cluebox.vala @@ -11,16 +11,14 @@ public class Gnonograms.ClueBox : Gtk.Widget { const int PIX_TO_PANGO_FONT = 1024 / 2; - public unowned View view { get; construct; } public bool holds_column_clues { get; construct; } public uint n_cells { get; set; default = 0; }// The number of cells each clue addresses, monitored by clues public double cell_size { get; set; } public Pango.FontDescription font_desc { get; set; } private Gee.ArrayList clues; - public ClueBox (View _view, bool _holds_column_clues) { + public ClueBox (bool _holds_column_clues) { Object ( - view: _view, holds_column_clues: _holds_column_clues ); } @@ -41,10 +39,10 @@ public class Gnonograms.ClueBox : Gtk.Widget { if (holds_column_clues) { hexpand = false; - view.controller.notify ["columns"].connect (add_remove_clues); + // view.controller.notify ["columns"].connect (add_remove_clues); } else { vexpand = false; - view.controller.notify ["rows"].connect (add_remove_clues); + // view.controller.notify ["rows"].connect (add_remove_clues); } notify["cell-size"].connect (update_size_request); @@ -86,9 +84,9 @@ public class Gnonograms.ClueBox : Gtk.Widget { } } - private void add_remove_clues () { - var new_n_clues = holds_column_clues ? view.controller.columns : view.controller.rows; - var new_n_cells = holds_column_clues ? view.controller.rows : view.controller.columns; + public void on_dimensions_changed (uint rows, uint cols) { + var new_n_clues = holds_column_clues ? cols : rows; + var new_n_cells = holds_column_clues ? rows : cols; if (n_cells != new_n_cells) { n_cells = new_n_cells; From 5cd52bf4b70484817eff0840f85a8382569ab1aa Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 8 Dec 2024 19:03:49 +0000 Subject: [PATCH 120/142] Add two Application signals --- src/Application.vala | 5 +++++ src/Controller.vala | 11 +++++------ src/Model.vala | 4 ++-- src/View.vala | 7 +++++-- src/widgets/Cellgrid.vala | 19 +++++++++++-------- 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index 4102451..e8de843 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -48,9 +48,14 @@ namespace Gnonograms { public GLib.Settings saved_state; public GLib.Settings settings; + + public class App : Gtk.Application { private Controller controller; + public signal void game_state_changed (GameState gs); + public signal void dimensions_changed (uint rows, uint cols); + public App () { Object ( application_id: Config.APP_ID, diff --git a/src/Controller.vala b/src/Controller.vala index 2c5212d..842a0f3 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -7,6 +7,7 @@ public class Gnonograms.Controller : GLib.Object { public signal void quit_app (); + // public signal void changed_dimensions (uint rows, uint cols); public Gtk.Window window { get { return (Gtk.Window)view;}} public GameState game_state { get; set; } @@ -38,6 +39,7 @@ public class Gnonograms.Controller : GLib.Object { public string current_game_path { get; set; default = ""; } private string saved_games_folder; private string? temporary_game_path = null; + private Gnonograms.App app = (Gnonograms.App) (Application.get_default ()); construct { game_name = _(UNTITLED_NAME); @@ -59,12 +61,11 @@ public class Gnonograms.Controller : GLib.Object { } }); - notify["columns"].connect (on_dimensions_changed); - notify["rows"].connect (on_dimensions_changed); - notify["current_game_path"].connect (() => { view.update_title (); }); + notify["rows"].connect (on_dimensions_changed); + notify["columns"].connect (on_dimensions_changed); var data_home_folder_current = Path.build_path ( Path.DIR_SEPARATOR_S, @@ -144,9 +145,7 @@ public class Gnonograms.Controller : GLib.Object { private void on_dimensions_changed () { solver = new Solver (dimensions); game_name = _(UNTITLED_NAME); - - model.on_dimensions_changed (rows, columns); - view.on_dimensions_changed (rows, columns); + app.dimensions_changed (rows, columns); } private void new_or_random_game () { diff --git a/src/Model.vala b/src/Model.vala index d8c53b1..3a96c7d 100644 --- a/src/Model.vala +++ b/src/Model.vala @@ -39,8 +39,8 @@ public class Gnonograms.Model : GLib.Object { construct { make_data_arrays (); - // controller.notify["rows"].connect (on_changed_dimensions); - // controller.notify["columns"].connect (on_changed_dimensions); + var app = (Gnonograms.App) Application.get_default (); + app.dimensions_changed.connect (on_dimensions_changed); controller.notify["game-state"].connect (() => { changed (); }); diff --git a/src/View.vala b/src/View.vala index 73ff5a2..d8ba1bc 100644 --- a/src/View.vala +++ b/src/View.vala @@ -138,6 +138,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } construct { + var app = (Gnonograms.App) Application.get_default (); title = _("Gnonograms"); set_default_size (DEFAULT_WIDTH, DEFAULT_HEIGHT); var view_actions = new GLib.SimpleActionGroup (); @@ -363,6 +364,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { update_title (); }); + app.dimensions_changed.connect (on_dimensions_changed); + // notify["readonly"].connect (() => { // save_game_button.sensitive = readonly; // }); @@ -425,14 +428,14 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } public void update_clues_from_solution () { - for (int r = 0; r < controller.dimensions.height; r++) { + for (int r = 0; r < controller.rows; r++) { row_clue_box.update_clue_text ( r, model.get_label_text_from_solution (r, false) ); } - for (int c = 0; c < controller.dimensions.width; c++) { + for (int c = 0; c < controller.columns; c++) { column_clue_box.update_clue_text ( c, model.get_label_text_from_solution (c, true) diff --git a/src/widgets/Cellgrid.vala b/src/widgets/Cellgrid.vala index 020add9..0602cef 100644 --- a/src/widgets/Cellgrid.vala +++ b/src/widgets/Cellgrid.vala @@ -72,6 +72,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } construct { + var app = ((Gnonograms.App)(Application.get_default ())); hexpand = true; vexpand = true; _current_cell = NULL_CELL; @@ -91,9 +92,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { queue_draw (); }); - view.controller.notify["game-state"].connect (() => { - on_game_state_changed (); - }); + app.game_state_changed.connect (on_game_state_changed); + app.dimensions_changed.connect (on_dimensions_changed); view.model.changed.connect (() => { if (!dirty) { @@ -104,8 +104,6 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { settings.changed["filled-color"].connect (set_colors); settings.changed["empty-color"].connect (set_colors); - // view.controller.notify["rows"].connect (queue_allocate); - // view.controller.notify["columns"].connect (queue_allocate); } public void on_dimensions_changed (uint rows, uint cols) { @@ -125,14 +123,19 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { colors[setting, (int) CellState.UNKNOWN].parse (Gnonograms.UNKNOWN_COLOR); colors[setting, (int) CellState.EMPTY].parse (settings.get_string ("empty-color")); colors[setting, (int) CellState.FILLED].parse (settings.get_string ("filled-color")); - on_game_state_changed (); + update_colors (); queue_draw (); return Source.REMOVE; }); } - private void on_game_state_changed () { - var gs = view.controller.game_state; + private GameState gs; + private void on_game_state_changed (GameState gs) { + this.gs = gs; + update_colors (); + } + + private void update_colors () { unknown_color = colors[(int)gs, (int)CellState.UNKNOWN]; fill_color = colors[(int)gs, (int)CellState.FILLED]; empty_color = colors[(int)gs, (int)CellState.EMPTY]; From 3ed68a6734a0b929fd6af42908db54cd6c36b08f Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Sun, 8 Dec 2024 19:10:00 +0000 Subject: [PATCH 121/142] Controller: Emit app signal when game state changes --- src/Controller.vala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Controller.vala b/src/Controller.vala index 842a0f3..bd281f4 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -65,7 +65,10 @@ public class Gnonograms.Controller : GLib.Object { view.update_title (); }); notify["rows"].connect (on_dimensions_changed); - notify["columns"].connect (on_dimensions_changed); + notify["columns"].connect (on_dimensions_changed); + notify["game-state"].connect (() => { + app.game_state_changed (game_state); + }); var data_home_folder_current = Path.build_path ( Path.DIR_SEPARATOR_S, From 51fc8a95324d41c4fe776d140bebad8bfdca71c9 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Mon, 9 Dec 2024 18:38:25 +0000 Subject: [PATCH 122/142] Move headerbar stuff into separate file, start to debug --- ...com.github.jeremypw.gnonograms.gschema.xml | 2 +- meson.build | 1 + src/Application.vala | 2 +- src/Controller.vala | 72 ++-- src/HeaderBar/HeaderBarFactory.vala | 224 ++++++++++++ src/View.vala | 330 ++++++------------ src/misc/Constants.vala | 2 +- src/misc/Enums.vala | 14 +- src/misc/Structs.vala | 76 ++-- src/misc/utils.vala | 15 +- src/objects/Move.vala | 71 ++-- src/services/Filereader.vala | 2 +- src/services/Filewriter.vala | 4 +- src/services/History.vala | 171 +++++---- src/services/Solver.vala | 10 +- src/widgets/Cellgrid.vala | 87 ++++- 16 files changed, 663 insertions(+), 420 deletions(-) create mode 100644 src/HeaderBar/HeaderBarFactory.vala diff --git a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml index f0e2292..5baa362 100644 --- a/data/schemas/com.github.jeremypw.gnonograms.gschema.xml +++ b/data/schemas/com.github.jeremypw.gnonograms.gschema.xml @@ -13,7 +13,7 @@ - + diff --git a/meson.build b/meson.build index 8d274fc..1db4c96 100644 --- a/meson.build +++ b/meson.build @@ -43,6 +43,7 @@ executable ( 'src/View.vala', 'src/Model.vala', + 'src/HeaderBar/HeaderBarFactory.vala', 'src/HeaderBar/HeaderButton.vala', 'src/HeaderBar/PopoverButton.vala', 'src/HeaderBar/ProgressIndicator.vala', diff --git a/src/Application.vala b/src/Application.vala index e8de843..5c81c15 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -9,7 +9,7 @@ namespace Gnonograms { SETTING, SOLVING, GENERATING, - UNDEFINED = 99; + LOAD_SAVE; } public const string ACTION_GROUP = "win"; diff --git a/src/Controller.vala b/src/Controller.vala index bd281f4..21d9b1d 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -51,8 +51,14 @@ public class Gnonograms.Controller : GLib.Object { #if WITH_DEBUGGING view.debug_request.connect (on_debug_request); #endif + app.game_state_changed.connect ((gs) => { + if (gs != game_state) { + game_state = gs; + } + }); + notify["game-state"].connect (() => { - if (game_state != GameState.UNDEFINED) { /* Do not clear on save */ + if (game_state != GameState.LOAD_SAVE) { /* Do not clear on save */ clear_history (); } @@ -65,7 +71,7 @@ public class Gnonograms.Controller : GLib.Object { view.update_title (); }); notify["rows"].connect (on_dimensions_changed); - notify["columns"].connect (on_dimensions_changed); + notify["columns"].connect (on_dimensions_changed); notify["game-state"].connect (() => { app.game_state_changed (game_state); }); @@ -182,7 +188,8 @@ public class Gnonograms.Controller : GLib.Object { private void new_game () { clear (); - game_state = GameState.SETTING; + app.game_state_changed (SETTING); + // game_state = GameState.SETTING; game_name = _(UNTITLED_NAME); } @@ -201,23 +208,25 @@ public class Gnonograms.Controller : GLib.Object { view.show_working (cancellable, (_("Generating"))); generator.generate.begin ((obj, res) => { var success = generator.generate.end (res); + GameState new_game_state; if (success) { - model.set_solution_from_array (generator.get_solution ()); - game_state = GameState.SOLVING; - view.update_clues_from_solution (); - view.game_grade = generator.solution_grade; + model.set_solution_from_array (generator.get_solution ()); + new_game_state = GameState.SOLVING; + view.update_clues_from_solution (); + view.game_grade = generator.solution_grade; + } else { + clear (); + new_game_state = GameState.SETTING; + if (cancellable.is_cancelled ()) { + view.send_notification (_("Game generation was cancelled")); } else { - clear (); - game_state = GameState.SETTING; - if (cancellable.is_cancelled ()) { - view.send_notification (_("Game generation was cancelled")); - } else { - view.send_notification (_("Failed to generate game of required grade")); - } + view.send_notification (_("Failed to generate game of required grade")); } + } - view.end_working (); - generator = null; + app.game_state_changed (new_game_state); + view.end_working (); + generator = null; }); } @@ -240,6 +249,7 @@ public class Gnonograms.Controller : GLib.Object { } private async bool restore_game () { + warning ("restore game"); if (temporary_game_path != null) { var current_game_file = File.new_for_path (temporary_game_path); return yield load_game_async (current_game_file); @@ -251,7 +261,7 @@ public class Gnonograms.Controller : GLib.Object { private async string? write_game (string? path, bool save_state = false) { Filewriter? file_writer = null; var gs = game_state; - game_state = GameState.UNDEFINED; + app.game_state_changed (LOAD_SAVE); file_writer = new Filewriter ( window, dimensions, @@ -259,11 +269,12 @@ public class Gnonograms.Controller : GLib.Object { view.get_clues (true), history, !model.solution_is_blank () - ); + ) { + difficulty = view.game_grade, + game_state = this.game_state, + working = model.copy_working_data () + }; - file_writer.difficulty = view.game_grade; - file_writer.game_state = gs; - file_writer.working = model.copy_working_data (); if (file_writer.save_solution) { file_writer.solution = model.copy_solution_data (); } @@ -290,7 +301,7 @@ public class Gnonograms.Controller : GLib.Object { return null; } finally { - game_state = gs; + app.game_state_changed (gs); } return file_writer.game_path; @@ -308,8 +319,7 @@ public class Gnonograms.Controller : GLib.Object { private async bool load_game_async (File? game) { Filereader? reader = null; var gs = game_state; - - game_state = GameState.UNDEFINED; + app.game_state_changed (LOAD_SAVE); clear_history (); reader = new Filereader (); try { @@ -337,15 +347,16 @@ public class Gnonograms.Controller : GLib.Object { return false; } finally { - game_state = gs; + app.game_state_changed (gs); } if (reader.valid && (yield load_common (reader))) { - if (reader.state != GameState.UNDEFINED) { - game_state = reader.state; - } else { - game_state = GameState.SOLVING; - } + // if (reader.state != GameState.UNDEFINED) { + // game_state = reader.state; + app.game_state_changed (reader.state); + // } else { + // game_state = GameState.SOLVING; + // } history.from_string (reader.moves); if (history.can_go_back) { @@ -531,6 +542,7 @@ public class Gnonograms.Controller : GLib.Object { } public void open_game () { + warning ("Controller: open game"); load_game_async.begin (null); /* Filereader will request load location */ } diff --git a/src/HeaderBar/HeaderBarFactory.vala b/src/HeaderBar/HeaderBarFactory.vala new file mode 100644 index 0000000..f1acb3c --- /dev/null +++ b/src/HeaderBar/HeaderBarFactory.vala @@ -0,0 +1,224 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: 2010-2024 Jeremy Wootten + * + * Authored by: Jeremy Wootten + */ + +public class Gnonograms.HeaderBarFactory : Object { + + public View view { get; construct; } + + private Gtk.HeaderBar header_bar; + private Gtk.Label title_label; + private Gtk.Stack progress_stack; + private ProgressIndicator progress_indicator; + private Gtk.Button generate_button; + private Gtk.Button undo_button; + private Gtk.Button redo_button; + private Gtk.Button check_correct_button; + private Gtk.Button hint_button; + private Granite.ModeSwitch mode_switch; + private AppPopover app_popover; + private Gtk.Button auto_solve_button; + private Gtk.Button restart_button; + + public HeaderBarFactory (Gnonograms.View view) { + Object ( + view: view + ); + } + + construct { + var app = (Gnonograms.App) Application.get_default (); + header_bar = new Gtk.HeaderBar (); + undo_button = new HeaderButton ( + "edit-undo-symbolic", + ACTION_PREFIX + ACTION_UNDO, + _("Undo Last Move") + ); + redo_button = new HeaderButton ( + "edit-redo-symbolic", + ACTION_PREFIX + ACTION_REDO, + _("Redo Last Move") + ); + check_correct_button = new HeaderButton ( + "media-seek-backward-symbolic", + ACTION_PREFIX + ACTION_CHECK_ERRORS, + _("Check for Errors") + ); + restart_button = new RestartButton ( + "view-refresh-symbolic", + ACTION_PREFIX + ACTION_RESTART, + _("Start again") + ) { + margin_end = 12, + margin_start = 12, + }; + hint_button = new HeaderButton ( + "help-contents-symbolic", + ACTION_PREFIX + ACTION_HINT, + _("Suggest next move") + ); + auto_solve_button = new HeaderButton ( + "computer-symbolic", + ACTION_PREFIX + ACTION_COMPUTER_SOLVE, + _("Check whether design is solvable") + ); + generate_button = new HeaderButton ( + "list-add", + ACTION_PREFIX + ACTION_GENERATING_MODE, + _("Generate New Puzzle") + ); + + app_popover = new AppPopover (view.controller); + + var menu_button = new Gtk.MenuButton () { + tooltip_markup = Granite.markup_accel_tooltip ( + app.get_accels_for_action ( + ACTION_PREFIX + ACTION_OPTIONS), + _("Options") + ), + icon_name = "open-menu-symbolic", + valign = Gtk.Align.CENTER, + popover = app_popover + }; + + // Unable to set markup on Granite.ModeSwitch so fake a Granite accelerator tooltip for now. + mode_switch = new Granite.ModeSwitch.from_icon_name ( + "edit-symbolic", + "system-run-symbolic" + ) { + margin_end = 12, + margin_start = 12, + valign = Gtk.Align.CENTER, + primary_icon_tooltip_text = "%s\n%s".printf (_("Edit a Game"), "Ctrl + 1"), + secondary_icon_tooltip_text = "%s\n%s".printf (_("Manually Solve"), "Ctrl + 2") + }; + + mode_switch.notify["active"].connect (() => { + app.game_state_changed (mode_switch.active ? GameState.SOLVING : GameState.SETTING); + // controller.game_state = mode_switch.active ? GameState.SOLVING : GameState.SETTING; + }); + + progress_indicator = new ProgressIndicator (); + + title_label = new Gtk.Label ("Gnonograms") { + use_markup = true, + xalign = 0.5f + }; + title_label.add_css_class (Granite.STYLE_CLASS_H3_LABEL); + + progress_stack = new Gtk.Stack () { + halign = Gtk.Align.CENTER, + }; + progress_stack.add_named (progress_indicator, "Progress"); + progress_stack.add_named (title_label, "Title"); + progress_stack.add_named (new Gtk.Label (""), "None"); + progress_stack.set_visible_child_name ("Title"); + + header_bar = new Gtk.HeaderBar () { + show_title_buttons = true, + title_widget = progress_stack + }; + header_bar.add_css_class ("gnonograms-header"); + header_bar.pack_start (generate_button); + header_bar.pack_start (hint_button); + header_bar.pack_start (restart_button); + header_bar.pack_start (undo_button); + header_bar.pack_start (redo_button); + header_bar.pack_start (check_correct_button); + header_bar.pack_end (menu_button); + header_bar.pack_end (mode_switch); + header_bar.pack_end (auto_solve_button); + + view.bind_property ( + "restart-destructive", + restart_button, "restart-destructive", + BindingFlags.SYNC_CREATE + ); + + } + + public Gtk.HeaderBar get_headerbar () { + return header_bar; + } + + public void update (GameState gs) { + if (gs == GENERATING) { + generate_button.sensitive = false; + return; + } + + generate_button.sensitive = true; + + + var is_solving = gs == SOLVING; + var is_setting = gs == SETTING; + var sensitive = (is_setting || is_solving); + + mode_switch.active = !is_setting; + mode_switch.sensitive = sensitive; + // restart_button. + undo_button.sensitive = sensitive && view.can_go_back; + redo_button.sensitive = sensitive && view.can_go_forward; + check_correct_button.sensitive = ( + sensitive && + gs == GameState.SOLVING && + view.can_go_back + ); + + hint_button.sensitive = sensitive && is_solving; + auto_solve_button.sensitive = is_setting; + } + + public void popdown_menus () { + app_popover.popdown (); + } + + public void on_can_go_changed (bool forward, bool back) { + warning ("Headbar_factory: on can go changed"); + check_correct_button.sensitive = back; + undo_button.sensitive = back; + redo_button.sensitive = forward; + +// - notify["can-go-back"].connect (() => { +// - check_correct_button.sensitive = can_go_back && +// - controller.game_state == GameState.SOLVING; +// - undo_button.sensitive = can_go_back; +// - /* May be destructive even if no history (e.g. after automatic solve) */ +// - restart_destructive |= can_go_back; +// - }); +// - +// - notify["can-go-forward"].connect (() => { +// - redo_button.sensitive = can_go_forward; +// - }); + } + + public void update_title (string name, string path, Difficulty grade) { + title_label.label = name; + title_label.tooltip_text = path; + if (grade != UNDEFINED) { + progress_stack.set_visible_child_name ("Title"); + } else { + progress_stack.set_visible_child_name ("None"); + } + } + + public void show_working (string text) { + progress_indicator.text = text; + } + + public void hide_progress (Difficulty game_grade) { + if (game_grade != Difficulty.UNDEFINED) { + progress_stack.set_visible_child_name ("Title"); + } else { + progress_stack.set_visible_child_name ("None"); + } + } + + public void show_progress (Cancellable? cancellable) { + progress_indicator.cancellable = cancellable; + progress_stack.set_visible_child_name ("Progress"); + } +} diff --git a/src/View.vala b/src/View.vala index d8ba1bc..f84629e 100644 --- a/src/View.vala +++ b/src/View.vala @@ -45,38 +45,41 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public Model model { get; construct; } public Controller controller { get; construct; } - public Cell current_cell { get; set; } - public Cell previous_cell { get; set; } + public SimpleActionGroup view_actions { get; construct; } + + public Cell? current_cell { get; set; } + public Cell? previous_cell { get; set; } public Difficulty generator_grade { get; set; } - public Difficulty game_grade { get; set; default = Difficulty.UNDEFINED;} + public Difficulty game_grade { get; set; } public string game_name { get { return controller.game_name; } } public bool readonly { get; set; default = false;} public bool can_go_back { get; set; } public bool can_go_forward { get; set; } public bool restart_destructive { get; set; default = false;} - public SimpleActionGroup view_actions { get; construct; } + private ClueBox row_clue_box; private ClueBox column_clue_box; private CellGrid cell_grid; - private ProgressIndicator progress_indicator; + private Gtk.MenuButton menu_button; - private CellState drawing_with_state = UNDEFINED; - private Gtk.HeaderBar header_bar; - private Granite.ModeSwitch mode_switch; + + private HeaderBarFactory headerbar_factory; + // private Granite.ModeSwitch mode_switch; private Gtk.Grid main_grid; private Adw.ToastOverlay toast_overlay; - private Gtk.Stack progress_stack; - private Gtk.Label title_label; - private Gtk.Button generate_button; - private Gtk.Button undo_button; - private Gtk.Button redo_button; - private Gtk.Button check_correct_button; - private Gtk.Button hint_button; - private AppPopover app_popover; - private Gtk.Button auto_solve_button; - private Gtk.Button restart_button; - private uint drawing_with_key; + + + // private Gtk.Button generate_button; + // private Gtk.Button undo_button; + // private Gtk.Button redo_button; + // private Gtk.Button check_correct_button; + // private Gtk.Button hint_button; + // private AppPopover app_popover; + // private Gtk.Button auto_solve_button; + // private Gtk.Button restart_button; + private uint drawing_with_key = 0; + private CellState drawing_with_state = INVALID; private uint paint_fill_key = Gdk.keyval_from_name ("f"); private uint paint_empty_key = Gdk.keyval_from_name ("e"); private uint paint_unknown_key = Gdk.keyval_from_name ("x"); @@ -152,103 +155,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { app.set_accels_for_action (ACTION_PREFIX + action, accels_array); } - undo_button = new HeaderButton ( - "edit-undo-symbolic", - ACTION_PREFIX + ACTION_UNDO, - _("Undo Last Move") - ); - redo_button = new HeaderButton ( - "edit-redo-symbolic", - ACTION_PREFIX + ACTION_REDO, - _("Redo Last Move") - ); - check_correct_button = new HeaderButton ( - "media-seek-backward-symbolic", - ACTION_PREFIX + ACTION_CHECK_ERRORS, - _("Check for Errors") - ); - restart_button = new RestartButton ( - "view-refresh-symbolic", - ACTION_PREFIX + ACTION_RESTART, - _("Start again") - ) { - margin_end = 12, - margin_start = 12, - }; - hint_button = new HeaderButton ( - "help-contents-symbolic", - ACTION_PREFIX + ACTION_HINT, - _("Suggest next move") - ); - auto_solve_button = new HeaderButton ( - "computer-symbolic", - ACTION_PREFIX + ACTION_COMPUTER_SOLVE, - _("Check whether design is solvable") - ); - generate_button = new HeaderButton ( - "list-add", - ACTION_PREFIX + ACTION_GENERATING_MODE, - _("Generate New Puzzle") - ); + headerbar_factory = new HeaderBarFactory (this); - app_popover = new AppPopover (controller); - - menu_button = new Gtk.MenuButton () { - tooltip_markup = Granite.markup_accel_tooltip ( - app.get_accels_for_action ( - ACTION_PREFIX + ACTION_OPTIONS), - _("Options") - ), - icon_name = "open-menu-symbolic", - valign = Gtk.Align.CENTER, - popover = app_popover - }; - - // Unable to set markup on Granite.ModeSwitch so fake a Granite accelerator tooltip for now. - mode_switch = new Granite.ModeSwitch.from_icon_name ( - "edit-symbolic", - "system-run-symbolic" - ) { - margin_end = 12, - margin_start = 12, - valign = Gtk.Align.CENTER, - primary_icon_tooltip_text = "%s\n%s".printf (_("Edit a Game"), "Ctrl + 1"), - secondary_icon_tooltip_text = "%s\n%s".printf (_("Manually Solve"), "Ctrl + 2") - }; - - progress_indicator = new ProgressIndicator (); - - title_label = new Gtk.Label ("Gnonograms") { - use_markup = true, - xalign = 0.5f - }; - title_label.add_css_class (Granite.STYLE_CLASS_H3_LABEL); - - progress_stack = new Gtk.Stack () { - halign = Gtk.Align.CENTER, - }; - progress_stack.add_named (progress_indicator, "Progress"); - progress_stack.add_named (title_label, "Title"); - progress_stack.add_named (new Gtk.Label (""), "None"); - progress_stack.set_visible_child_name ("Title"); - - header_bar = new Gtk.HeaderBar () { - show_title_buttons = true, - title_widget = progress_stack - }; - header_bar.add_css_class ("gnonograms-header"); - header_bar.pack_start (generate_button); - header_bar.pack_start (hint_button); - header_bar.pack_start (restart_button); - header_bar.pack_start (undo_button); - header_bar.pack_start (redo_button); - header_bar.pack_start (check_correct_button); - header_bar.pack_end (menu_button); - header_bar.pack_end (mode_switch); - header_bar.pack_end (auto_solve_button); - - - set_titlebar (header_bar); + set_titlebar (headerbar_factory.get_headerbar ()); row_clue_box = new ClueBox (false); column_clue_box = new ClueBox (true); @@ -324,11 +233,14 @@ public class Gnonograms.View : Gtk.ApplicationWindow { child = toast_overlay; var flags = BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE; - bind_property ( - "restart-destructive", - restart_button, "restart-destructive", - BindingFlags.SYNC_CREATE - ); + // bind_property ( + // "restart-destructive", + // restart_button, "restart-destructive", + // BindingFlags.SYNC_CREATE + // ); + current_cell = Cell () { row = 0, col = 0, state = UNKNOWN }; + previous_cell = current_cell.clone (); + bind_property ( "current-cell", cell_grid, "current-cell", @@ -340,54 +252,31 @@ public class Gnonograms.View : Gtk.ApplicationWindow { BindingFlags.BIDIRECTIONAL ); - mode_switch.notify["active"].connect (() => { - controller.game_state = mode_switch.active ? GameState.SOLVING : GameState.SETTING; - }); - controller.notify["game-state"].connect (() => { - if (controller.game_state != GameState.UNDEFINED) { - update_all_labels_completeness (); - } - - // Avoid updating header bar while generating otherwise generation will be cancelled. - // Headerbar will update when generation finished. - if (controller.game_state != GameState.GENERATING) { + update_all_labels_completeness (); + // // Avoid updating header bar while generating otherwise generation will be cancelled. + // // Headerbar will update when generation finished. + // if (controller.game_state != GameState.GENERATING) { update_header_bar (); - } + // } }); - controller.notify["game-name"].connect (() => { - update_title (); - }); + controller.notify["game-name"].connect (update_title); - notify["game-grade"].connect (() => { - update_title (); - }); app.dimensions_changed.connect (on_dimensions_changed); // notify["readonly"].connect (() => { // save_game_button.sensitive = readonly; // }); - - notify["can-go-back"].connect (() => { - check_correct_button.sensitive = can_go_back && - controller.game_state == GameState.SOLVING; - undo_button.sensitive = can_go_back; - /* May be destructive even if no history (e.g. after automatic solve) */ - restart_destructive |= can_go_back; - }); - - notify["can-go-forward"].connect (() => { - redo_button.sensitive = can_go_forward; - }); - + notify["game-grade"].connect (update_title); + notify["can-go-back"].connect (on_can_go_changed); + notify["can-go-forward"].connect (on_can_go_changed); notify["current-cell"].connect (() => { highlight_labels (previous_cell, false); highlight_labels (current_cell, true); - - if (current_cell != NULL_CELL && - drawing_with_state != CellState.UNDEFINED) { + if (current_cell != null && + drawing_with_state != CellState.INVALID) { make_move_at_cell (); } @@ -445,10 +334,10 @@ public class Gnonograms.View : Gtk.ApplicationWindow { update_all_labels_completeness (); } - public void make_move (Move m) { - if (!m.is_null ()) { - update_current_and_model (m.cell.state, m.cell); - } + public void make_move (Move m) requires (m.is_valid ()) { + // if (!m.is_null ()) { + update_current_and_model (m.cell.state, m.cell); + // } } public void send_notification (string text) { @@ -457,75 +346,75 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public void show_working (Cancellable cancellable, string text = "") { cell_grid.frozen = true; // Do not show model updates - progress_indicator.text = text; schedule_show_progress (cancellable); + headerbar_factory.show_working (text); } public void end_working () { cell_grid.frozen = false; // Show model updates again - if (progress_timeout_id > 0) { Source.remove (progress_timeout_id); progress_timeout_id = 0; } - if (game_grade != Difficulty.UNDEFINED) { - progress_stack.set_visible_child_name ("Title"); - } else { - progress_stack.set_visible_child_name ("None"); - } + headerbar_factory.hide_progress (game_grade); update_all_labels_completeness (); update_header_bar (); } private void update_header_bar () { - mode_switch.active = controller.game_state != GameState.SETTING; - - switch (controller.game_state) { - case GameState.SETTING: - set_buttons_sensitive (true); - break; - case GameState.SOLVING: - set_buttons_sensitive (true); - - break; - case GameState.GENERATING: - set_buttons_sensitive (false); - - break; - default: - break; - } + var gs = controller.game_state; + restart_destructive = !model.is_blank (gs); + headerbar_factory.update (gs); + // mode_switch.active = controller.game_state != GameState.SETTING; + + // switch (controller.game_state) { + // case GameState.SETTING: + // set_buttons_sensitive (true); + // break; + // case GameState.SOLVING: + // set_buttons_sensitive (true); + + // break; + // case GameState.GENERATING: + // set_buttons_sensitive (false); + + // break; + // default: + // break; + // } } public void update_title () { - title_label.label = game_name; - title_label.tooltip_text = controller.current_game_path; - if (game_grade != Difficulty.UNDEFINED) { - progress_stack.set_visible_child_name ("Title"); - } else { - progress_stack.set_visible_child_name ("None"); - } + headerbar_factory.update_title (game_name, controller.current_game_path, game_grade); } - private void set_buttons_sensitive (bool sensitive) { - generate_button.sensitive = controller.game_state != GameState.GENERATING; - mode_switch.sensitive = sensitive; - restart_destructive = sensitive && !model.is_blank (controller.game_state); - undo_button.sensitive = sensitive && can_go_back; - redo_button.sensitive = sensitive && can_go_forward; - check_correct_button.sensitive = - sensitive && - controller.game_state == GameState.SOLVING && - can_go_back; - - hint_button.sensitive = sensitive && controller.game_state == GameState.SOLVING; - auto_solve_button.sensitive = controller.game_state == GameState.SETTING; + private void on_can_go_changed () { + headerbar_factory.on_can_go_changed (can_go_forward, can_go_back); } - private void highlight_labels (Cell c, bool is_highlight) { + // private void set_buttons_sensitive (bool sensitive) { + // generate_button.sensitive = controller.game_state != GameState.GENERATING; + // mode_switch.sensitive = sensitive; + // restart_destructive = sensitive && !model.is_blank (controller.game_state); + // undo_button.sensitive = sensitive && can_go_back; + // redo_button.sensitive = sensitive && can_go_forward; + // check_correct_button.sensitive = + // sensitive && + // controller.game_state == GameState.SOLVING && + // can_go_back; + + // hint_button.sensitive = sensitive && controller.game_state == GameState.SOLVING; + // auto_solve_button.sensitive = controller.game_state == GameState.SETTING; + // } + + private void highlight_labels (Cell? c, bool is_highlight) { /* If c is NULL_CELL then will unhighlight all labels */ + if (c == null) { + return; + } + row_clue_box.highlight (c.row, is_highlight); column_clue_box.highlight (c.col, is_highlight); } @@ -554,12 +443,12 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private void make_move_at_cell ( CellState state = drawing_with_state, - Cell target = current_cell - ) { - if (target == NULL_CELL) { - return; - } - + Cell? target = current_cell + ) requires (target != null) { + // if (target == NULL_CELL) { + // return; + // } +warning ("make move at cell"); var prev_state = model.get_data_for_cell (target); var cell = update_current_and_model (state, target); @@ -606,8 +495,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { Priority.HIGH_IDLE, PROGRESS_DELAY_MSEC, () => { - progress_indicator.cancellable = cancellable; - progress_stack.set_visible_child_name ("Progress"); + headerbar_factory.show_progress (cancellable); progress_timeout_id = 0; return false; } @@ -615,7 +503,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } private void stop_painting () { - drawing_with_state = CellState.UNDEFINED; + drawing_with_state = CellState.INVALID; drawing_with_key = 0; } @@ -640,7 +528,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } private void action_preferences () { - app_popover.popdown (); + headerbar_factory.popdown_menus (); var dialog = new PreferencesDialog () { transient_for = this, title = _("Preferences") @@ -742,19 +630,19 @@ public class Gnonograms.View : Gtk.ApplicationWindow { move_cursor (0, 1); } private void move_cursor (int row_delta, int col_delta) { - if (current_cell == NULL_CELL) { - update_current_cell ({0, 0, CellState.UNDEFINED}); + if (current_cell == null) { + update_current_cell ({ 0, 0, CellState.INVALID }); return; } - Cell target = { - current_cell.row + row_delta, - current_cell.col + col_delta, - CellState.UNDEFINED + var target = Cell () { + row = current_cell.row + row_delta, + col = current_cell.col + col_delta, + state = CellState.INVALID }; - if (target.row >= controller.dimensions.height || - target.col >= controller.dimensions.width) { + if (target.row >= controller.rows || + target.col >= controller.columns) { return; } diff --git a/src/misc/Constants.vala b/src/misc/Constants.vala index 40fbe85..34bfaa8 100644 --- a/src/misc/Constants.vala +++ b/src/misc/Constants.vala @@ -5,7 +5,7 @@ * Authored by: Jeremy Wootten */ namespace Gnonograms { - public const Cell NULL_CELL = { uint.MAX, uint.MAX, CellState.UNDEFINED }; + // public const Cell NULL_CELL = { uint.MAX, uint.MAX, CellState.UNDEFINED }; public const uint MAXSIZE = 54; // max number rows or columns public const uint MINSIZE = 5; // Change to 1 when debugging public const uint SIZESTEP = 5; // Change to 1 when debugging diff --git a/src/misc/Enums.vala b/src/misc/Enums.vala index 3ae4c6e..627b856 100644 --- a/src/misc/Enums.vala +++ b/src/misc/Enums.vala @@ -51,13 +51,13 @@ namespace Gnonograms { } } - public enum CellState { - UNKNOWN, - EMPTY, - FILLED, - COMPLETED, - UNDEFINED; - } + // public enum CellState { + // UNKNOWN, + // EMPTY, + // FILLED, + // COMPLETED, + // UNDEFINED; + // } public enum SolverState { ERROR = 0, diff --git a/src/misc/Structs.vala b/src/misc/Structs.vala index 3a5abbc..f782d3e 100644 --- a/src/misc/Structs.vala +++ b/src/misc/Structs.vala @@ -26,44 +26,44 @@ public class Gnonograms.Block { } } -public struct Gnonograms.Cell { - public uint row; - public uint col; - public CellState state; - - public bool same_coords (Cell c) { - return (this.row == c.row && this.col == c.col); - } - - public bool equal (Cell b) { - return ( - this.row == b.row && - this.col == b.col && - this.state == b.state - ); - - } - - public Cell inverse () { - Cell c = {row, col, CellState.UNKNOWN }; - - if (this.state == CellState.EMPTY) { - c.state = CellState.FILLED; - } else { - c.state = CellState.EMPTY; - } - - return c; - } - - public Cell clone () { - return { row, col, state }; - } - - public string to_string () { - return "Row %u, Col %u, State %s".printf (row, col, state.to_string ()); - } -} +// public struct Gnonograms.Cell { +// public uint row; +// public uint col; +// public CellState state; + +// public bool same_coords (Cell c) { +// return (this.row == c.row && this.col == c.col); +// } + +// public bool equal (Cell b) { +// return ( +// this.row == b.row && +// this.col == b.col && +// this.state == b.state +// ); + +// } + +// public Cell inverse () { +// Cell c = {row, col, CellState.UNKNOWN }; + +// if (this.state == CellState.EMPTY) { +// c.state = CellState.FILLED; +// } else { +// c.state = CellState.EMPTY; +// } + +// return c; +// } + +// public Cell clone () { +// return { row, col, state }; +// } + +// public string to_string () { +// return "Row %u, Col %u, State %s".printf (row, col, state.to_string ()); +// } +// } public struct Gnonograms.Dimensions { uint width; diff --git a/src/misc/utils.vala b/src/misc/utils.vala index 5d8b4ad..6be1be4 100644 --- a/src/misc/utils.vala +++ b/src/misc/utils.vala @@ -145,7 +145,7 @@ namespace Gnonograms.Utils { public string block_string_from_cellstate_array (CellState[] cellstates) { StringBuilder sb = new StringBuilder (""); - CellState count_state = CellState.UNDEFINED; + CellState count_state = CellState.INVALID; int count = 0, blocks = 0; bool counting = false; foreach (var state in cellstates) { @@ -156,16 +156,15 @@ namespace Gnonograms.Utils { blocks++; } else if (count_state == CellState.UNKNOWN) { sb.append ("?" + BLOCKSEPARATOR); - } counting = false; - count_state = CellState.UNDEFINED; + count_state = CellState.INVALID; count = 0; break; case CellState.FILLED: - if (count_state == CellState.UNDEFINED) { + if (count_state == CellState.INVALID) { count = 0; counting = true; } else if (count_state == CellState.UNKNOWN) { @@ -178,7 +177,7 @@ namespace Gnonograms.Utils { break; case CellState.UNKNOWN: - if (count_state == CellState.UNDEFINED) { + if (count_state == CellState.INVALID) { counting = true; } else if (count_state == CellState.FILLED) { sb.append (count.to_string () + BLOCKSEPARATOR); @@ -199,7 +198,9 @@ namespace Gnonograms.Utils { } else if (count_state == CellState.UNKNOWN) { sb.append ("?" + BLOCKSEPARATOR); blocks++; - } if (blocks == 0) { + } + + if (blocks == 0) { sb.append ("0"); } else { sb.truncate (sb.len - BLOCKSEPARATOR.length); // remove trailing seperator @@ -212,7 +213,7 @@ namespace Gnonograms.Utils { CellState[] cs = {}; string[] blocks = remove_blank_lines (s.split_set (BLOCKSEPARATOR)); foreach (var block in blocks) { - cs += (CellState)(int.parse (block)).clamp (0, CellState.UNDEFINED); + cs += (CellState)(int.parse (block)).clamp (0, CellState.INVALID); } return cs; diff --git a/src/objects/Move.vala b/src/objects/Move.vala index a7dc987..46a84a7 100644 --- a/src/objects/Move.vala +++ b/src/objects/Move.vala @@ -5,12 +5,12 @@ * Authored by: Jeremy Wootten */ public class Gnonograms.Move { - public static Move null_move = new Move (NULL_CELL, CellState.UNDEFINED); + // public static Move null_move = new Move (NULL_CELL, CellState.UNDEFINED); public Cell cell; public CellState previous_state; - public Move (Cell _cell, CellState _previous_state) { + public Move.from_cell (Cell _cell, CellState _previous_state) { cell = Cell () { row =_cell.row, col =_cell.col, @@ -19,47 +19,72 @@ public class Gnonograms.Move { previous_state = _previous_state; } + + public Move (uint _row, uint _col, CellState _state, CellState _previous_state) { + cell = Cell () { + row =_row, + col =_col, + state = _state + }; - public bool equal (Move m) { - return m.cell.equal (cell) && m.previous_state == previous_state; + previous_state = _previous_state; } - public Move clone () { - return new Move (this.cell.clone (), this.previous_state); + public bool is_valid () { + return ( + cell.row < MAXSIZE && + cell.col < MAXSIZE && + cell.state < CellState.COMPLETED && + previous_state < CellState.COMPLETED + ); + } + + public bool equal (Move? m) { + return m != null && (m.cell.equal (cell) && m.previous_state == previous_state); } - public bool is_null () { - return equal (Move.null_move); + public Move clone () { + return new Move.from_cell (this.cell.clone (), this.previous_state); } + // public bool is_null () { + // return equal (Move.null_move); + // } + public string to_string () { return "%u,%u,%u,%u".printf (cell.row, cell.col, cell.state, previous_state); } - public static Move from_string (string? s) { - if (s == null) { - return Move.null_move; - } + public static Move? from_string (string s) throws ConvertError { + // if (s == null) { + // return Move.null_move; + // } var parts = s.split (","); if (parts == null || parts.length != 4) { - return Move.null_move; + // return Move.null_move; + throw new ConvertError.FAILED ("Incorrect number of parts"); } var row = (uint)(int.parse (parts[0])); var col = (uint)(int.parse (parts[1])); - var state = (Gnonograms.CellState)(int.parse (parts[2])); - var previous_state = (Gnonograms.CellState)(int.parse (parts[3])); + var state = (uint)(int.parse (parts[2])); + var previous_state = (uint)(int.parse (parts[3])); - if (row > Gnonograms.MAXSIZE || - col > Gnonograms.MAXSIZE || - state == Gnonograms.CellState.UNDEFINED || - previous_state == Gnonograms.CellState.UNDEFINED) { + // if (row > MAXSIZE || + // col > MAXSIZE || + // state > CellState.COMPLETED || + // previous_state > CellState.COMPLETED) { - return Move.null_move; - } + // throw new ConvertError.FAILED ("Invalid location or state"); + // } - Cell c = {row, col, state}; - return new Move (c, previous_state); + // Cell c = {row, col, state}; + var mv = new Move (row, col, state, previous_state); + if (mv.is_valid ()) { + return mv; + } else { + throw new ConvertError.FAILED ("Invalid parameters"); + } } } diff --git a/src/services/Filereader.vala b/src/services/Filereader.vala index a8632dd..5702746 100644 --- a/src/services/Filereader.vala +++ b/src/services/Filereader.vala @@ -8,7 +8,7 @@ public class Gnonograms.Filereader : Object { public string err_msg = ""; public File? game_file { get; set; default = null;} - public GameState state { get; private set; default = GameState.UNDEFINED;} + public GameState state { get; private set; } public int rows { get; private set; default = 0;} public int cols { get; private set; default = 0;} diff --git a/src/services/Filewriter.vala b/src/services/Filewriter.vala index 4f53018..5d5d425 100644 --- a/src/services/Filewriter.vala +++ b/src/services/Filewriter.vala @@ -9,7 +9,7 @@ public class Gnonograms.Filewriter : Object { public History? history { get; construct; } public Gtk.Window? parent { get; construct; } public Difficulty difficulty { get; set; default = Difficulty.UNDEFINED;} - public GameState game_state { get; set; default = GameState.UNDEFINED;} + public GameState game_state { get; set; } public My2DCellArray? solution { get; set; default = null;} public My2DCellArray? working { get; set; default = null;} public uint rows { get; construct; } @@ -160,8 +160,6 @@ public class Gnonograms.Filewriter : Object { string? name = null) throws Error { if (working == null) { throw (new IOError.NOT_INITIALIZED ("No working grid to save")); - } else if (game_state == GameState.UNDEFINED) { - throw (new IOError.NOT_INITIALIZED ("No game state to save")); } yield write_game_file (save_dir_path, path, name ); diff --git a/src/services/History.vala b/src/services/History.vala index 2e84ef0..19d4bc0 100644 --- a/src/services/History.vala +++ b/src/services/History.vala @@ -5,8 +5,71 @@ * Authored by: Jeremy Wootten */ public class Gnonograms.History : GLib.Object { - public bool can_go_back { get; private set; } - public bool can_go_forward { get; private set; } + private class HistoryStack : Object { + public bool empty { + get { + return stack.is_empty; + } + } + // private set; } + + private Gee.Deque stack; + + construct { + stack = new Gee.LinkedList (); + } + + public void push_move (Move mv) requires (mv.is_valid ()) { + stack.offer_head (mv); + // empty = false; + } + + public Move? peek_move () { + // if (empty) { + // return null; + // } else { + return stack.peek_head (); + // } + } + + public Move? pop_move () { + return stack.poll_head (); + // Move mv; + // if (!empty) { + // mv = + // } + + // empty = stack.is_empty; + // return mv; + } + + public void clear () { + stack.clear (); + // empty = true; + } + + public string to_string () { + var sb = new StringBuilder (""); + foreach (Move mv in stack) { /* iterates from head backwards */ + sb.prepend (mv.to_string () + ";"); + } + + sb.append ("\n"); + return sb.str; + } + } + public bool can_go_back { + get { + return !back_stack.empty; + } + } + // ; private set; } + public bool can_go_forward { + get { + return !forward_stack.empty; + } + } + // private set; } private HistoryStack back_stack; private HistoryStack forward_stack; @@ -15,13 +78,13 @@ public class Gnonograms.History : GLib.Object { back_stack = new HistoryStack (); forward_stack = new HistoryStack (); - back_stack.notify["empty"].connect (() => { - can_go_back = !back_stack.empty; - }); + // back_stack.notify["empty"].connect (() => { + // can_go_back = !back_stack.empty; + // }); - forward_stack.notify["empty"].connect (() => { - can_go_forward = !forward_stack.empty; - }); + // forward_stack.notify["empty"].connect (() => { + // can_go_forward = !forward_stack.empty; + // }); } public void clear_all () { @@ -29,16 +92,23 @@ public class Gnonograms.History : GLib.Object { back_stack.clear (); } - public void record_move (Cell cell, CellState previous_state) { - var new_move = new Gnonograms.Move (cell, previous_state); - if (new_move.cell.state != CellState.UNDEFINED) { - Move last_move = back_stack.peek_move (); - if (last_move.equal (new_move)) { + public void record_move (Cell? cell, CellState previous_state) { +warning ("record move"); + if (cell == null) { + warning ("IGNORE null cell"); + return; + } + + var new_move = new Gnonograms.Move.from_cell (cell, previous_state); + // if (new_move.cell.state != CellState.UNDEFINED) { + Move? last_move = back_stack.peek_move (); + if (new_move.equal (last_move)) { + warning ("ignore same as last move"); return; } forward_stack.clear (); - } + // } back_stack.push_move (new_move); } @@ -81,75 +151,32 @@ public class Gnonograms.History : GLib.Object { } } - private void add_to_stack_from_string (string? s, bool back) { + private bool add_to_stack_from_string (string? s, bool back) { if (s == null) { - return; + return false; } var moves_s = s.split (";"); if (moves_s == null) { - return; + return false; } foreach (string move_s in moves_s) { - var move = Move.from_string (move_s); - if (move != null) { - if (back) { - back_stack.push_move (move); - } else { - forward_stack.push_move (move); + try { + var move = Move.from_string (move_s); + if (move != null) { + if (back) { + back_stack.push_move (move); + } else { + forward_stack.push_move (move); + } } + } catch (Error e) { + warning ("Could not convert %s to Move. %s", move_s, e.message); + return false; } } - } - private class HistoryStack : Object { - public bool empty { get; private set; } - - private Gee.Deque stack; - - construct { - stack = new Gee.LinkedList (); - } - - public void push_move (Move mv) { - if (!mv.is_null ()) { - stack.offer_head (mv); - empty = false; - } - } - - public Move peek_move () { - if (empty) { - return Move.null_move; - } else { - return stack.peek_head (); - } - } - - public Move pop_move () { - Move mv = Move.null_move; - if (!empty) { - mv = stack.poll_head (); - } - - empty = stack.is_empty; - return mv; - } - - public void clear () { - stack.clear (); - empty = true; - } - - public string to_string () { - var sb = new StringBuilder (""); - foreach (Move mv in stack) { /* iterates from head backwards */ - sb.prepend (mv.to_string () + ";"); - } - - sb.append ("\n"); - return sb.str; - } + return true; } } diff --git a/src/services/Solver.vala b/src/services/Solver.vala index ad47252..47839d6 100644 --- a/src/services/Solver.vala +++ b/src/services/Solver.vala @@ -268,7 +268,7 @@ var row = r.is_column ? i : r.index; var col = r.is_column ? r.index : i; Cell c = {row, col, r_state}; - moves.add (new Move (c, csa[i])); + moves.add (new Move.from_cell (c, csa[i])); changed = true; } } @@ -300,7 +300,7 @@ var row = r.is_column ? i : r.index; var col = r.is_column ? r.index : i; Cell c = {row, col, r_state}; - moves.add (new Move (c, csa[i])); + moves.add (new Move.from_cell (c, csa[i])); break; } } @@ -373,19 +373,19 @@ int empty = 0; int min_empty_cells = int.MAX; int changed_count = 0; - Cell best_guess = NULL_CELL; + Cell? best_guess = null; state = SolverState.UNDEFINED; while (state == SolverState.UNDEFINED) { changed_count++; if (!guesser.next_guess ()) { state = SolverState.NO_SOLUTION; - if (best_guess.equal (NULL_CELL)) { // No improvement from last round + if (best_guess == null) { // No improvement from last round break; } else { grid.set_data_from_cell (best_guess); guesser = new Guesser (grid, false); - best_guess = NULL_CELL; + best_guess = null; changed_count = 0; if (!guesser.next_guess ()) { warning ("No next guess"); diff --git a/src/widgets/Cellgrid.vala b/src/widgets/Cellgrid.vala index 0602cef..183a699 100644 --- a/src/widgets/Cellgrid.vala +++ b/src/widgets/Cellgrid.vala @@ -4,12 +4,70 @@ * * Authored by: Jeremy Wootten */ + +public enum Gnonograms.CellState { + UNKNOWN, + EMPTY, + FILLED, + COMPLETED, + INVALID +} + +public struct Gnonograms.Cell { + public uint row; + public uint col; + public CellState state; + + public bool same_coords (Cell c) { + return (this.row == c.row && this.col == c.col); + } + + public bool equal (Cell? b) { + return ( + b != null && + this.row == b.row && + this.col == b.col && + this.state == b.state + ); + + } + + public bool same_place (Cell? b) { + return ( + b != null && + this.row == b.row && + this.col == b.col + ); + + } + + public Cell inverse () { + Cell c = {row, col, CellState.UNKNOWN }; + + if (this.state == CellState.EMPTY) { + c.state = CellState.FILLED; + } else { + c.state = CellState.EMPTY; + } + + return c; + } + + public Cell clone () { + return { row, col, state }; + } + + public string to_string () { + return "Row %u, Col %u, State %s".printf (row, col, state.to_string ()); + } +} + public class Gnonograms.CellGrid : Gtk.DrawingArea { public signal void leave (); public unowned View view { get; construct; } - public Cell current_cell { get; set; } - public Cell previous_cell { get; set; } + public Cell? current_cell { get; set; } + public Cell? previous_cell { get; set; } public bool frozen { get; set; } public bool draw_only { get; set; default = false;} /* Could have more options for cell pattern*/ @@ -75,7 +133,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { var app = ((Gnonograms.App)(Application.get_default ())); hexpand = true; vexpand = true; - _current_cell = NULL_CELL; + current_cell = null; colors = new Gdk.RGBA[2, 3]; grid_color.parse (Gnonograms.GRID_COLOR); cell_pattern_type = CellPatternType.CELL; @@ -177,13 +235,18 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { highlight_pattern = new CellPattern.highlight (cell_width, cell_height); } - private void draw_func (Gtk.DrawingArea drawing_area, Cairo.Context cr, int x, int y) { + private void draw_func ( + Gtk.DrawingArea drawing_area, + Cairo.Context cr, + int x, + int y + ) { + dirty = false; - if (array != null) { /* Note, even tho' array holds CellStates, its iterator returns Cells */ - foreach (Cell c in array) { - bool highlight = (c.row == current_cell.row && c.col == current_cell.col); + foreach (Cell? c in array) { + bool highlight = c.same_place (current_cell); draw_cell (cr, c, highlight); } } @@ -211,7 +274,11 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { /* Construct cell beneath pointer */ Cell cell = {r, c, array.get_data_from_rc (r, c)}; if (!cell.equal (current_cell)) { - previous_cell = current_cell.clone (); + if (current_cell == null) { + previous_cell = null; + } else { + previous_cell = current_cell.clone (); + } current_cell = cell.clone (); } @@ -329,8 +396,8 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } private void on_leave_notify () { - previous_cell = NULL_CELL; - current_cell = NULL_CELL; + previous_cell = null; + current_cell = null; leave (); return; } From 90a5446ab85e51fc5f629fc7f907cc17feab35ba Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 10 Dec 2024 12:12:13 +0000 Subject: [PATCH 123/142] Fix forward/back --- src/Controller.vala | 28 +++++++++++++++----------- src/HeaderBar/HeaderBarFactory.vala | 17 ++++++++-------- src/View.vala | 31 +++++++++++++++++------------ src/services/History.vala | 12 +++++++++++ 4 files changed, 55 insertions(+), 33 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index 21d9b1d..c573048 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -36,6 +36,18 @@ public class Gnonograms.Controller : GLib.Object { private Solver? solver; private SimpleRandomGameGenerator? generator; private Gnonograms.History history; + public bool can_go_back { + get { + return history.can_go_back; + } + } + // private set; } + public bool can_go_forward { + get { + return history.can_go_forward; + } + } + // set; } public string current_game_path { get; set; default = ""; } private string saved_games_folder; private string? temporary_game_path = null; @@ -45,7 +57,7 @@ public class Gnonograms.Controller : GLib.Object { game_name = _(UNTITLED_NAME); model = new Model (this); view = new View (model, this); - history = new Gnonograms.History (); + history = new History (); view.close_request.connect (on_view_deleted); #if WITH_DEBUGGING @@ -129,17 +141,9 @@ public class Gnonograms.Controller : GLib.Object { BindingFlags.SYNC_CREATE ); - history.bind_property ( - "can-go-back", - view, - "can-go-back", - BindingFlags.SYNC_CREATE - ); - history.bind_property ( - "can-go-forward", - view, "can-go-forward", - BindingFlags.SYNC_CREATE - ); + history.can_go_changed.connect ((forward, back) => { + view.on_can_go_changed (forward, back); + }); restore_game.begin ((obj, res) => { if (!restore_game.end (res)) { diff --git a/src/HeaderBar/HeaderBarFactory.vala b/src/HeaderBar/HeaderBarFactory.vala index f1acb3c..2bb154b 100644 --- a/src/HeaderBar/HeaderBarFactory.vala +++ b/src/HeaderBar/HeaderBarFactory.vala @@ -144,7 +144,8 @@ public class Gnonograms.HeaderBarFactory : Object { return header_bar; } - public void update (GameState gs) { + public void on_game_state_changed (GameState gs) { +warning ("headerbar update"); if (gs == GENERATING) { generate_button.sensitive = false; return; @@ -160,13 +161,13 @@ public class Gnonograms.HeaderBarFactory : Object { mode_switch.active = !is_setting; mode_switch.sensitive = sensitive; // restart_button. - undo_button.sensitive = sensitive && view.can_go_back; - redo_button.sensitive = sensitive && view.can_go_forward; - check_correct_button.sensitive = ( - sensitive && - gs == GameState.SOLVING && - view.can_go_back - ); + // undo_button.sensitive = sensitive && view.can_go_back; + // redo_button.sensitive = sensitive && view.can_go_forward; + // check_correct_button.sensitive = ( + // sensitive && + // gs == GameState.SOLVING && + // view.can_go_back + // ); hint_button.sensitive = sensitive && is_solving; auto_solve_button.sensitive = is_setting; diff --git a/src/View.vala b/src/View.vala index f84629e..6b3be15 100644 --- a/src/View.vala +++ b/src/View.vala @@ -53,8 +53,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public Difficulty game_grade { get; set; } public string game_name { get { return controller.game_name; } } public bool readonly { get; set; default = false;} - public bool can_go_back { get; set; } - public bool can_go_forward { get; set; } public bool restart_destructive { get; set; default = false;} @@ -209,6 +207,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { button_controller.set_button (0); // Listen to any button main_grid.add_controller (button_controller); button_controller.pressed.connect ((n_press, x, y) => { + // warning ("button press"); var button = button_controller.get_current_button (); var shift = (SHIFT_MASK in button_controller.get_current_event_state ()); var set_unknown = (n_press == 2 || button == Gdk.BUTTON_MIDDLE); @@ -253,11 +252,14 @@ public class Gnonograms.View : Gtk.ApplicationWindow { ); controller.notify["game-state"].connect (() => { + var gs = controller.game_state; update_all_labels_completeness (); // // Avoid updating header bar while generating otherwise generation will be cancelled. // // Headerbar will update when generation finished. // if (controller.game_state != GameState.GENERATING) { - update_header_bar (); + restart_destructive = !model.is_blank (gs); + headerbar_factory.on_game_state_changed (gs); + // update_header_bar (gs); // } }); @@ -270,9 +272,10 @@ public class Gnonograms.View : Gtk.ApplicationWindow { // save_game_button.sensitive = readonly; // }); notify["game-grade"].connect (update_title); - notify["can-go-back"].connect (on_can_go_changed); - notify["can-go-forward"].connect (on_can_go_changed); + // notify["can-go-back"].connect (on_can_go_changed); + // notify["can-go-forward"].connect (on_can_go_changed); notify["current-cell"].connect (() => { + // warning ("current cell changed"); highlight_labels (previous_cell, false); highlight_labels (current_cell, true); if (current_cell != null && @@ -360,13 +363,13 @@ public class Gnonograms.View : Gtk.ApplicationWindow { headerbar_factory.hide_progress (game_grade); update_all_labels_completeness (); - update_header_bar (); + // update_header_bar (); } - private void update_header_bar () { - var gs = controller.game_state; - restart_destructive = !model.is_blank (gs); - headerbar_factory.update (gs); + // private void update_header_bar () { + // var gs = controller.game_state; + // restart_destructive = !model.is_blank (gs); + // headerbar_factory.on_game_state_changed (gs); // mode_switch.active = controller.game_state != GameState.SETTING; // switch (controller.game_state) { @@ -384,14 +387,15 @@ public class Gnonograms.View : Gtk.ApplicationWindow { // default: // break; // } - } + // } public void update_title () { headerbar_factory.update_title (game_name, controller.current_game_path, game_grade); } - private void on_can_go_changed () { - headerbar_factory.on_can_go_changed (can_go_forward, can_go_back); + public void on_can_go_changed (bool forward, bool back) { + warning ("on can go changed"); + headerbar_factory.on_can_go_changed (forward, back); } // private void set_buttons_sensitive (bool sensitive) { @@ -678,6 +682,7 @@ warning ("make move at cell"); return; } +warning ("paint cell state"); drawing_with_state = cs; make_move_at_cell (); diff --git a/src/services/History.vala b/src/services/History.vala index 19d4bc0..49c35e9 100644 --- a/src/services/History.vala +++ b/src/services/History.vala @@ -58,6 +58,7 @@ public class Gnonograms.History : GLib.Object { return sb.str; } } + public bool can_go_back { get { return !back_stack.empty; @@ -73,6 +74,8 @@ public class Gnonograms.History : GLib.Object { private HistoryStack back_stack; private HistoryStack forward_stack; + + public signal void can_go_changed (bool forward, bool back); construct { back_stack = new HistoryStack (); @@ -87,9 +90,14 @@ public class Gnonograms.History : GLib.Object { // }); } + private void signal_can_go_changed () { + can_go_changed (!forward_stack.empty, !back_stack.empty ); + } + public void clear_all () { forward_stack.clear (); back_stack.clear (); + signal_can_go_changed (); } public void record_move (Cell? cell, CellState previous_state) { @@ -111,11 +119,13 @@ warning ("record move"); // } back_stack.push_move (new_move); + signal_can_go_changed (); } public Move pop_next_move () { Move mv = forward_stack.pop_move (); back_stack.push_move (mv); + signal_can_go_changed (); return mv; } @@ -124,6 +134,7 @@ warning ("record move"); /* Record copy otherwise it will be altered by next line*/ forward_stack.push_move (mv.clone ()); mv.cell.state = mv.previous_state; + signal_can_go_changed (); return mv; } @@ -177,6 +188,7 @@ warning ("record move"); } } + signal_can_go_changed (); return true; } } From 4cfa8f438b5ee9e59f68f74d4ad4ccd2b493e746 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 10 Dec 2024 15:09:48 +0000 Subject: [PATCH 124/142] Fix mode switch --- src/Controller.vala | 31 +++++++++++++++-------------- src/HeaderBar/HeaderBarFactory.vala | 13 +++++++++--- src/View.vala | 15 ++++++++------ 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index c573048..acf7d82 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -10,7 +10,7 @@ public class Gnonograms.Controller : GLib.Object { // public signal void changed_dimensions (uint rows, uint cols); public Gtk.Window window { get { return (Gtk.Window)view;}} - public GameState game_state { get; set; } + public GameState game_state { get; private set; } public Dimensions dimensions { get { return {columns, rows}; @@ -36,13 +36,13 @@ public class Gnonograms.Controller : GLib.Object { private Solver? solver; private SimpleRandomGameGenerator? generator; private Gnonograms.History history; - public bool can_go_back { + public bool can_go_back { get { return history.can_go_back; } } // private set; } - public bool can_go_forward { + public bool can_go_forward { get { return history.can_go_forward; } @@ -63,20 +63,21 @@ public class Gnonograms.Controller : GLib.Object { #if WITH_DEBUGGING view.debug_request.connect (on_debug_request); #endif - app.game_state_changed.connect ((gs) => { - if (gs != game_state) { - game_state = gs; - } - }); - + // app.game_state_changed.connect ((gs) => { + // if (gs != game_state) { + // game_state = gs; + // } + // }); + notify["game-state"].connect (() => { - if (game_state != GameState.LOAD_SAVE) { /* Do not clear on save */ - clear_history (); - } + warning ("notify gamestate - now %s", game_state.to_string ()); + // if (game_state != GameState.LOAD_SAVE) { /* Do not clear on save */ + // clear_history (); + // } - if (game_state == GameState.GENERATING) { - on_new_random_request (); - } + // if (game_state == GameState.GENERATING) { + // on_new_random_request (); + // } }); notify["current_game_path"].connect (() => { diff --git a/src/HeaderBar/HeaderBarFactory.vala b/src/HeaderBar/HeaderBarFactory.vala index 2bb154b..e8821ac 100644 --- a/src/HeaderBar/HeaderBarFactory.vala +++ b/src/HeaderBar/HeaderBarFactory.vala @@ -97,7 +97,14 @@ public class Gnonograms.HeaderBarFactory : Object { }; mode_switch.notify["active"].connect (() => { - app.game_state_changed (mode_switch.active ? GameState.SOLVING : GameState.SETTING); + if (mode_switch.active) { + warning ("solving"); + mode_switch.activate_action (ACTION_PREFIX + ACTION_SOLVING_MODE, null); + } else { + warning ("setting"); + mode_switch.activate_action (ACTION_PREFIX + ACTION_SETTING_MODE, null); + } + // app.game_state_changed (mode_switch.active ? GameState.SOLVING : GameState.SETTING); // controller.game_state = mode_switch.active ? GameState.SOLVING : GameState.SETTING; }); @@ -145,7 +152,7 @@ public class Gnonograms.HeaderBarFactory : Object { } public void on_game_state_changed (GameState gs) { -warning ("headerbar update"); +// warning ("headerbar update"); if (gs == GENERATING) { generate_button.sensitive = false; return; @@ -178,7 +185,7 @@ warning ("headerbar update"); } public void on_can_go_changed (bool forward, bool back) { - warning ("Headbar_factory: on can go changed"); + // warning ("Headbar_factory: on can go changed"); check_correct_button.sensitive = back; undo_button.sensitive = back; redo_button.sensitive = forward; diff --git a/src/View.vala b/src/View.vala index 6b3be15..283acc0 100644 --- a/src/View.vala +++ b/src/View.vala @@ -251,14 +251,15 @@ public class Gnonograms.View : Gtk.ApplicationWindow { BindingFlags.BIDIRECTIONAL ); - controller.notify["game-state"].connect (() => { - var gs = controller.game_state; + app.game_state_changed.connect ((gs) => { + // controller.notify["game-state"].connect (() => { update_all_labels_completeness (); // // Avoid updating header bar while generating otherwise generation will be cancelled. // // Headerbar will update when generation finished. // if (controller.game_state != GameState.GENERATING) { restart_destructive = !model.is_blank (gs); headerbar_factory.on_game_state_changed (gs); + // update_header_bar (gs); // } }); @@ -394,7 +395,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } public void on_can_go_changed (bool forward, bool back) { - warning ("on can go changed"); + // warning ("on can go changed"); headerbar_factory.on_can_go_changed (forward, back); } @@ -655,13 +656,15 @@ warning ("make move at cell"); } private void action_setting_mode () { - controller.game_state = GameState.SETTING; + controller.change_mode (SETTING); } private void action_solving_mode () { - controller.game_state = GameState.SOLVING; + // controller.game_state = GameState.SOLVING; + controller.change_mode (SOLVING); } private void action_generating_mode () { - controller.game_state = GameState.GENERATING; + // controller.game_state = GameState.GENERATING; + controller.change_mode (GENERATING); } private void paint_filled () { From f45739ea4326d9e169a7789798acce0b0afef464 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 10 Dec 2024 15:10:08 +0000 Subject: [PATCH 125/142] Fix save on quit + restore --- src/Application.vala | 5 +- src/Controller.vala | 102 ++++++++++++++++++++++++++--------- src/services/Filewriter.vala | 31 ++++++++--- 3 files changed, 104 insertions(+), 34 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index 5c81c15..1aa857c 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -74,8 +74,9 @@ namespace Gnonograms { SimpleAction quit_action = new SimpleAction ("quit", null); quit_action.activate.connect (() => { + warning ("quit action"); if (controller != null) { - controller.quit (); /* Will save state */ + controller.on_delete_request (); /* Will save state */ } }); @@ -95,7 +96,7 @@ namespace Gnonograms { public override void activate () { if (controller == null) { controller = new Controller (); - controller.quit_app.connect (quit); + // controller.quit_app.connect (quit); add_window (controller.window); } else { controller.window.present (); diff --git a/src/Controller.vala b/src/Controller.vala index acf7d82..85671a9 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -59,7 +59,9 @@ public class Gnonograms.Controller : GLib.Object { view = new View (model, this); history = new History (); - view.close_request.connect (on_view_deleted); + view.close_request.connect (() => { + return on_delete_request (); + }); #if WITH_DEBUGGING view.debug_request.connect (on_debug_request); #endif @@ -170,18 +172,42 @@ public class Gnonograms.Controller : GLib.Object { } } - public void quit () { + public void change_mode (GameState mode) { + warning ("change mode"); + switch (mode) { + case SETTING: + case SOLVING: + clear_history (); + game_state = mode; + break; + case GENERATING: + on_new_random_request (); + break; + default: + critical ("Unhandled mode change request"); + break; + } + } + + public void prepare_quit () { +warning ("quit - hold"); if (solver != null) { solver.cancel (); } /* If in middle of generating no defined game to save */ if (generator == null) { - save_game_state (); + app.hold (); + save_game_state.begin ((obj, res) => { + // Always quit for now + warning ("save game state end"); + warning ("release"); + app.release (); + app.quit (); + }); } else { generator.cancel (); + app.quit (); } - - quit_app (); } private void clear () { @@ -193,8 +219,8 @@ public class Gnonograms.Controller : GLib.Object { private void new_game () { clear (); - app.game_state_changed (SETTING); - // game_state = GameState.SETTING; + // app.game_state_changed (SETTING); + game_state = GameState.SETTING; game_name = _(UNTITLED_NAME); } @@ -207,9 +233,10 @@ public class Gnonograms.Controller : GLib.Object { generator = new SimpleRandomGameGenerator (dimensions, solver) { grade = generator_grade }; + game_name = _("Random pattern"); view.game_grade = Difficulty.UNDEFINED; - + game_state = GameState.GENERATING; view.show_working (cancellable, (_("Generating"))); generator.generate.begin ((obj, res) => { var success = generator.generate.end (res); @@ -229,24 +256,26 @@ public class Gnonograms.Controller : GLib.Object { } } - app.game_state_changed (new_game_state); view.end_working (); + game_state = new_game_state; // app.game_state_changed (new_game_state); + generator = null; }); } - private void save_game_state () { + private async void save_game_state () { +warning ("save game state"); if (temporary_game_path != null) { try { var current_game_file = File.new_for_path (temporary_game_path); current_game_file.@delete (); } catch (Error e) { /* Error normally thrown on first run */ - debug ("Error deleting temporary game file %s - %s", temporary_game_path, e.message); + warning ("Error deleting temporary game file %s - %s", temporary_game_path, e.message); } finally { - debug ("writing unsaved game to %s", temporary_game_path); + warning ("writing unsaved game to %s", temporary_game_path); /* Save solution and current state */ - write_game.begin (temporary_game_path, true); + yield write_game (temporary_game_path, true); } } else { warning ("No temporary game path"); @@ -256,6 +285,7 @@ public class Gnonograms.Controller : GLib.Object { private async bool restore_game () { warning ("restore game"); if (temporary_game_path != null) { + warning ("restore temp"); var current_game_file = File.new_for_path (temporary_game_path); return yield load_game_async (current_game_file); } else { @@ -264,9 +294,10 @@ public class Gnonograms.Controller : GLib.Object { } private async string? write_game (string? path, bool save_state = false) { +warning ("Controller: write game. game state %s", game_state.to_string ()); Filewriter? file_writer = null; - var gs = game_state; - app.game_state_changed (LOAD_SAVE); + + // app.game_state_changed (LOAD_SAVE); file_writer = new Filewriter ( window, dimensions, @@ -276,7 +307,7 @@ public class Gnonograms.Controller : GLib.Object { !model.solution_is_blank () ) { difficulty = view.game_grade, - game_state = this.game_state, + state = game_state, working = model.copy_working_data () }; @@ -285,10 +316,15 @@ public class Gnonograms.Controller : GLib.Object { } file_writer.is_readonly = is_readonly; + var gs = game_state; + game_state = LOAD_SAVE; try { if (save_state) { + warning ("saving state"); yield file_writer.write_position_file (saved_games_folder, path, game_name); + warning ("after write position file"); } else { + warning ("saving game"); yield file_writer.write_game_file ( saved_games_folder, path, @@ -302,31 +338,38 @@ public class Gnonograms.Controller : GLib.Object { _("Unable to save %s").printf (basename), e.message ); + } else { + warning ("CANCELLED"); } return null; } finally { - app.game_state_changed (gs); + game_state = gs; } return file_writer.game_path; } public void load_game (File? game) { +warning ("Controller: load game"); load_game_async.begin (game, (obj, res) => { if (!load_game_async.end (res)) { warning ("Load game failed"); new_or_random_game (); + } else { + warning ("loaded game. State %s", game_state.to_string ()); } }); } private async bool load_game_async (File? game) { +warning ("Controller: load game async"); Filereader? reader = null; - var gs = game_state; - app.game_state_changed (LOAD_SAVE); clear_history (); reader = new Filereader (); + var gs = game_state; + game_state = LOAD_SAVE; + // app.game_state_changed (LOAD_SAVE); try { yield reader.read ( window, @@ -350,15 +393,18 @@ public class Gnonograms.Controller : GLib.Object { } } + game_state = gs; return false; - } finally { - app.game_state_changed (gs); } if (reader.valid && (yield load_common (reader))) { + warning ("Valid and loaded common"); // if (reader.state != GameState.UNDEFINED) { // game_state = reader.state; - app.game_state_changed (reader.state); + warning ("reader state %s", reader.state.to_string ()); + // app.game_state_changed (reader.state); + // // } else { + game_state = reader.state; // } else { // game_state = GameState.SOLVING; // } @@ -515,15 +561,20 @@ public class Gnonograms.Controller : GLib.Object { } } - private bool on_view_deleted () { - quit (); - return Gdk.EVENT_PROPAGATE; + public bool on_delete_request () { + warning ("on delete request"); + prepare_quit (); // Async + warning ("on view deleted returning true"); + return true; } public async void save_game () { + warning ("Controller: save game"); if (is_readonly || current_game_path == "") { + warning ("save game as"); yield save_game_as (); } else { + warning ("write game"); var path = yield write_game (current_game_path, false); if (path != null && path != "") { current_game_path = path; @@ -533,6 +584,7 @@ public class Gnonograms.Controller : GLib.Object { } public async void save_game_as () { + warning ("Controller: save game as"); /* Filewriter will request save location, no solution saved as default */ var path = yield write_game (null, false); if (path != null) { diff --git a/src/services/Filewriter.vala b/src/services/Filewriter.vala index 5d5d425..dd989fc 100644 --- a/src/services/Filewriter.vala +++ b/src/services/Filewriter.vala @@ -9,7 +9,7 @@ public class Gnonograms.Filewriter : Object { public History? history { get; construct; } public Gtk.Window? parent { get; construct; } public Difficulty difficulty { get; set; default = Difficulty.UNDEFINED;} - public GameState game_state { get; set; } + public GameState state { get; set; } public My2DCellArray? solution { get; set; default = null;} public My2DCellArray? working { get; set; default = null;} public uint rows { get; construct; } @@ -55,6 +55,7 @@ public class Gnonograms.Filewriter : Object { string? path = null, string? _name = null ) throws Error { + warning ("writer write game"); if (_name != null) { name = _name; } else { @@ -96,13 +97,17 @@ public class Gnonograms.Filewriter : Object { throw new IOError.CANCELLED ("File exists"); } +warning ("game path %s", game_path); /* @game_path is local path, not a uri */ stream = FileStream.open (game_path, "w"); +warning ("opened stream"); if (stream == null) { + warning ("stream is null"); throw new IOError.FAILED ("Could not open filestream to %s".printf (game_path)); } if (name == null || name.length == 0) { + warning ("No name"); throw new IOError.NOT_INITIALIZED ("No name to save"); } @@ -118,6 +123,7 @@ public class Gnonograms.Filewriter : Object { } if (rows == 0 || cols == 0) { + warning ("no dimensions"); throw new IOError.NOT_INITIALIZED ("No dimensions to save"); } @@ -126,10 +132,12 @@ public class Gnonograms.Filewriter : Object { stream.printf ("%u\n", cols); if (row_clues.length == 0 || col_clues.length == 0) { + warning ("no clues"); throw new IOError.NOT_INITIALIZED ("No clues to save"); } if (row_clues.length != rows || col_clues.length != cols) { + warning ("mismatch"); throw new IOError.NOT_INITIALIZED ("Clues do not match dimensions"); } @@ -143,8 +151,11 @@ public class Gnonograms.Filewriter : Object { stream.printf ("%s\n", s); } +warning ("flush"); stream.flush (); +warning ("solution is %s", solution != null ? "NOT null" : "null"); +warning ("save_solution is %s", save_solution.to_string ()); if (solution != null && save_solution) { stream.printf ("[Solution grid]\n"); stream.printf ("%s", solution.to_string ()); @@ -152,22 +163,27 @@ public class Gnonograms.Filewriter : Object { stream.printf ("[Locked]\n"); stream.printf (is_readonly.to_string () + "\n"); +warning ("End of write game file"); } /*** Writes complete information to reload game state ***/ - public async void write_position_file (string? save_dir_path = null, - string? path = null, - string? name = null) throws Error { + public async void write_position_file ( + string? save_dir_path = null, + string? path = null, + string? name = null + ) throws Error { if (working == null) { + warning ("No working grid"); throw (new IOError.NOT_INITIALIZED ("No working grid to save")); } - +warning ("write position "); yield write_game_file (save_dir_path, path, name ); +warning ("after write game file"); stream.printf ("[Working grid]\n"); stream.printf (working.to_string ()); stream.printf ("[State]\n"); - stream.printf (game_state.to_string () + "\n"); - + stream.printf (state.to_string () + "\n"); +warning ("write state %s", state.to_string ()); if (name != _(UNTITLED_NAME)) { stream.printf ("[Original path]\n"); stream.printf (game_path.to_string () + "\n"); @@ -179,5 +195,6 @@ public class Gnonograms.Filewriter : Object { } stream.flush (); +warning ("end of write position"); } } From eb548595737de151aa186745bf66f043e1669356 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 10 Dec 2024 18:18:13 +0000 Subject: [PATCH 126/142] Use singletons for Controller, Mode, View --- src/Application.vala | 12 ++- src/Controller.vala | 128 +++++++++++++++++----------- src/HeaderBar/AppPopover.vala | 8 +- src/HeaderBar/HeaderBarFactory.vala | 5 +- src/HeaderBar/HeaderButton.vala | 3 +- src/Model.vala | 29 +++++-- src/View.vala | 90 +++++++++++-------- src/widgets/Cellgrid.vala | 41 +++++---- src/widgets/Cluebox.vala | 3 + 9 files changed, 194 insertions(+), 125 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index 1aa857c..07b4b34 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -51,10 +51,12 @@ namespace Gnonograms { public class App : Gtk.Application { + // public GameState game_state { get; private set; default = LOAD_SAVE; } + private Controller controller; - public signal void game_state_changed (GameState gs); - public signal void dimensions_changed (uint rows, uint cols); + // public signal void game_state_changed (GameState gs); + public App () { Object ( @@ -95,9 +97,13 @@ namespace Gnonograms { public override void activate () { if (controller == null) { - controller = new Controller (); + controller = Controller.get_default (); // controller.quit_app.connect (quit); add_window (controller.window); + // Only the controller should change the global game state. + // controller.notify["game-state"].connect (() => { + // game_state = controller.game_state; + // }); } else { controller.window.present (); } diff --git a/src/Controller.vala b/src/Controller.vala index 85671a9..3d3de22 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -6,36 +6,31 @@ */ public class Gnonograms.Controller : GLib.Object { - public signal void quit_app (); - // public signal void changed_dimensions (uint rows, uint cols); - - public Gtk.Window window { get { return (Gtk.Window)view;}} - public GameState game_state { get; private set; } - public Dimensions dimensions { - get { - return {columns, rows}; + public static Controller get_default () { + if (instance == null) { + instance = new Controller (); + instance.set_model_and_view (); } - } - public bool is_solving { - get { return game_state == SOLVING; } + return instance; } - public uint rows { get; set; } - public uint columns { get; set; } + private static Controller? instance = null; - public Difficulty generator_grade { get; set; } - public string game_name { get; set; } + public signal void quit_app (); + public signal void dimensions_changed (uint rows, uint cols); + // public signal void changed_dimensions (uint rows, uint cols); + public Gtk.Window window { get { return (Gtk.Window)view;}} + public GameState game_state { get; private set; } + public uint rows { get; private set; } + public uint columns { get; private set; } + public Difficulty generator_grade { get; private set; } + public string game_name { get; private set; } + private string current_game_path { get; private set; default = ""; } /* Any game that was not saved by this app is regarded as read only - any alterations * must be "Saved As" - which by default is writable. */ - public bool is_readonly { get; set; default = false;} - - public unowned View view {get; construct; } - private Model model; - private Solver? solver; - private SimpleRandomGameGenerator? generator; - private Gnonograms.History history; + public bool is_readonly { get; private set; default = false;} public bool can_go_back { get { return history.can_go_back; @@ -47,24 +42,37 @@ public class Gnonograms.Controller : GLib.Object { return history.can_go_forward; } } - // set; } - public string current_game_path { get; set; default = ""; } + + + // public bool is_solving { + // get { return game_state == SOLVING; } + // } + + private View view; + private Model model; + private Solver? solver; + private SimpleRandomGameGenerator? generator; + private Gnonograms.History history; + private string saved_games_folder; private string? temporary_game_path = null; private Gnonograms.App app = (Gnonograms.App) (Application.get_default ()); + private Controller () {} + + private Dimensions dimensions { + get { + return { columns, rows }; + } + } + construct { game_name = _(UNTITLED_NAME); - model = new Model (this); - view = new View (model, this); + // model = new Model (this); + history = new History (); - view.close_request.connect (() => { - return on_delete_request (); - }); -#if WITH_DEBUGGING - view.debug_request.connect (on_debug_request); -#endif + // app.game_state_changed.connect ((gs) => { // if (gs != game_state) { // game_state = gs; @@ -82,14 +90,12 @@ public class Gnonograms.Controller : GLib.Object { // } }); - notify["current_game_path"].connect (() => { - view.update_title (); - }); + notify["rows"].connect (on_dimensions_changed); notify["columns"].connect (on_dimensions_changed); - notify["game-state"].connect (() => { - app.game_state_changed (game_state); - }); + // notify["game-state"].connect (() => { + // app.game_state_changed (game_state); + // }); var data_home_folder_current = Path.build_path ( Path.DIR_SEPARATOR_S, @@ -120,16 +126,35 @@ public class Gnonograms.Controller : GLib.Object { settings.bind ("grade", this, "generator-grade", SettingsBindFlags.DEFAULT); settings.bind ("rows", this, "rows", SettingsBindFlags.DEFAULT); settings.bind ("columns", this, "columns", SettingsBindFlags.DEFAULT); + restore_game.begin ((obj, res) => { + if (!restore_game.end (res)) { + /* Error normally thrown if running without installing */ + warning ("Restoring game failed"); + // restore_dimensions (); + new_game (); + } + }); + } + + protected void set_model_and_view () { + // Needs to be done after Controller construction complete as they need a controller instance + model = Model.get_default (); + view = View.get_default (); + view.close_request.connect (() => { + return on_delete_request (); + }); +#if WITH_DEBUGGING + view.debug_request.connect (on_debug_request); +#endif view.present (); - /* - * This is very finicky. Bind size after present else set_titlebar gives us bad sizes - */ // TODO limit related to actual monitor dimensions view.default_height = saved_state.get_int ("window-height").clamp (64, 768); view.default_width = saved_state.get_int ("window-width").clamp (128, 1024); - + /* + * This is very finicky. Bind size after present else set_titlebar gives us bad sizes + */ saved_state.bind ("window-height", view, "default-height", SettingsBindFlags.SET); saved_state.bind ("window-width", view, "default-width", SettingsBindFlags.SET); @@ -144,24 +169,23 @@ public class Gnonograms.Controller : GLib.Object { BindingFlags.SYNC_CREATE ); - history.can_go_changed.connect ((forward, back) => { - view.on_can_go_changed (forward, back); + // notify["current-game-path"].connect (() => { + // view.update_title (game_name); + // }); + notify["game-name"].connect (() => { + view.update_title (game_name); }); - restore_game.begin ((obj, res) => { - if (!restore_game.end (res)) { - /* Error normally thrown if running without installing */ - warning ("Restoring game failed"); - // restore_dimensions (); - new_game (); - } + history.can_go_changed.connect ((forward, back) => { + view.on_can_go_changed (forward, back); }); } + private void on_dimensions_changed () { solver = new Solver (dimensions); game_name = _(UNTITLED_NAME); - app.dimensions_changed (rows, columns); + dimensions_changed (rows, columns); } private void new_or_random_game () { diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 2df7103..7ae9fd5 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -5,12 +5,8 @@ * Authored by: Jeremy Wootten */ public class Gnonograms.AppPopover : Gtk.Popover { - public Controller controller { get; construct; } - public AppPopover (Controller controller) { - Object ( - controller: controller - ); - } + private Controller controller = Controller.get_default (); + construct { var app = (Gtk.Application)(GLib.Application.get_default ()); diff --git a/src/HeaderBar/HeaderBarFactory.vala b/src/HeaderBar/HeaderBarFactory.vala index e8821ac..d52797f 100644 --- a/src/HeaderBar/HeaderBarFactory.vala +++ b/src/HeaderBar/HeaderBarFactory.vala @@ -22,6 +22,7 @@ public class Gnonograms.HeaderBarFactory : Object { private AppPopover app_popover; private Gtk.Button auto_solve_button; private Gtk.Button restart_button; + // private App app = (App)(Application.get_default ()); public HeaderBarFactory (Gnonograms.View view) { Object ( @@ -71,7 +72,7 @@ public class Gnonograms.HeaderBarFactory : Object { _("Generate New Puzzle") ); - app_popover = new AppPopover (view.controller); + app_popover = new AppPopover (); var menu_button = new Gtk.MenuButton () { tooltip_markup = Granite.markup_accel_tooltip ( @@ -152,7 +153,7 @@ public class Gnonograms.HeaderBarFactory : Object { } public void on_game_state_changed (GameState gs) { -// warning ("headerbar update"); +warning ("headerbar game state changed to %s", gs.to_string ()); if (gs == GENERATING) { generate_button.sensitive = false; return; diff --git a/src/HeaderBar/HeaderButton.vala b/src/HeaderBar/HeaderButton.vala index bc1cd15..c12c6b8 100644 --- a/src/HeaderBar/HeaderButton.vala +++ b/src/HeaderBar/HeaderButton.vala @@ -9,7 +9,8 @@ Object ( action_name: action_name, tooltip_markup: Granite.markup_accel_tooltip ( - View.app.get_accels_for_action (action_name), text + ((App)(Application.get_default ())).get_accels_for_action (action_name), + text ), valign: Gtk.Align.CENTER ); diff --git a/src/Model.vala b/src/Model.vala index 3a96c7d..5502b39 100644 --- a/src/Model.vala +++ b/src/Model.vala @@ -5,6 +5,16 @@ * Authored by: Jeremy Wootten */ public class Gnonograms.Model : GLib.Object { + public static Model get_default () { + if (instance == null) { + instance = new Model (); + } + + return instance; + } + + private static Model? instance; + public signal void changed (); public My2DCellArray display_data { @@ -19,7 +29,9 @@ public class Gnonograms.Model : GLib.Object { } } - public Controller controller { get; construct; } + // public Controller controller { get; construct; } + private Controller controller = Controller.get_default (); + private My2DCellArray solution_data { get; set; } private My2DCellArray working_data { get; set; } @@ -31,16 +43,17 @@ public class Gnonograms.Model : GLib.Object { } } - public Model (Controller controller) { - Object ( - controller: controller - ); - } + // public Model (Controller controller) { + // Object ( + // controller: controller + // ); + // } + private Model () {} construct { make_data_arrays (); - var app = (Gnonograms.App) Application.get_default (); - app.dimensions_changed.connect (on_dimensions_changed); + // var app = (Gnonograms.App) Application.get_default (); + controller.dimensions_changed.connect (on_dimensions_changed); controller.notify["game-state"].connect (() => { changed (); }); diff --git a/src/View.vala b/src/View.vala index 283acc0..806c414 100644 --- a/src/View.vala +++ b/src/View.vala @@ -6,13 +6,22 @@ */ public class Gnonograms.View : Gtk.ApplicationWindow { + public static View get_default () { + if (instance == null) { + instance = new View (); + } + + return instance; + } + + private static View? instance = null; + private const uint PROGRESS_DELAY_MSEC = 500; private const int DEFAULT_WIDTH = 900; private const int DEFAULT_HEIGHT = 700; private const uint DARK = Granite.Settings.ColorScheme.DARK; public static Gee.MultiMap action_accelerators; - public static Gtk.Application app; private static GLib.ActionEntry [] view_action_entries = { {ACTION_UNDO, action_undo}, {ACTION_REDO, action_redo}, @@ -43,8 +52,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public signal void debug_request (uint idx, bool is_column); #endif - public Model model { get; construct; } - public Controller controller { get; construct; } + // public Model model { get; construct; } + // public Controller controller { get; construct; } + public SimpleActionGroup view_actions { get; construct; } public Cell? current_cell { get; set; } @@ -55,6 +65,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public bool readonly { get; set; default = false;} public bool restart_destructive { get; set; default = false;} + private Controller controller = Controller.get_default (); + private Model model = Model.get_default (); private ClueBox row_clue_box; private ClueBox column_clue_box; @@ -81,17 +93,19 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private uint paint_fill_key = Gdk.keyval_from_name ("f"); private uint paint_empty_key = Gdk.keyval_from_name ("e"); private uint paint_unknown_key = Gdk.keyval_from_name ("x"); + // private App app = (App)(Application.get_default ()); + // public View (Model _model, Controller controller) { + // Object ( + // model: _model, + // controller: controller + // ); + // } - public View (Model _model, Controller controller) { - Object ( - model: _model, - controller: controller - ); - } + private View () {} static construct { action_accelerators = new Gee.HashMultiMap (); - app = (Gtk.Application)(Application.get_default ()); + #if WITH_DEBUGGING warning ("WITH DEBUGGING"); view_action_entries += ActionEntry () { @@ -159,7 +173,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { row_clue_box = new ClueBox (false); column_clue_box = new ClueBox (true); - cell_grid = new CellGrid (this); + cell_grid = new CellGrid (); cell_grid.bind_property ("cell-width", column_clue_box, "cell-size"); cell_grid.bind_property ("cell-height", row_clue_box, "cell-size"); @@ -214,7 +228,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { var set_empty = (button == Gdk.BUTTON_SECONDARY || button == Gdk.BUTTON_PRIMARY && shift); if (set_unknown) { // Clear current cell - drawing_with_state = controller.is_solving ? CellState.UNKNOWN : CellState.EMPTY; + drawing_with_state = controller.game_state == SOLVING ? CellState.UNKNOWN : CellState.EMPTY; } else { // Paint current cell drawing_with_state = set_empty ? CellState.EMPTY : CellState.FILLED; } @@ -251,28 +265,19 @@ public class Gnonograms.View : Gtk.ApplicationWindow { BindingFlags.BIDIRECTIONAL ); - app.game_state_changed.connect ((gs) => { + controller.notify["game-state"].connect (on_game_state_changed); + // app.game_state_changed.connect ((gs) => { // controller.notify["game-state"].connect (() => { - update_all_labels_completeness (); - // // Avoid updating header bar while generating otherwise generation will be cancelled. - // // Headerbar will update when generation finished. - // if (controller.game_state != GameState.GENERATING) { - restart_destructive = !model.is_blank (gs); - headerbar_factory.on_game_state_changed (gs); - - // update_header_bar (gs); - // } - }); - - controller.notify["game-name"].connect (update_title); + // }); - app.dimensions_changed.connect (on_dimensions_changed); + // controller.notify["game-name"].connect (update_title); + // app.dimensions_changed.connect (on_dimensions_changed); // notify["readonly"].connect (() => { // save_game_button.sensitive = readonly; // }); - notify["game-grade"].connect (update_title); + // notify["game-grade"].connect (update_title); // notify["can-go-back"].connect (on_can_go_changed); // notify["can-go-forward"].connect (on_can_go_changed); notify["current-cell"].connect (() => { @@ -300,12 +305,25 @@ public class Gnonograms.View : Gtk.ApplicationWindow { }); } - public void on_dimensions_changed (uint rows, uint cols) { - row_clue_box.on_dimensions_changed (rows, cols); - column_clue_box.on_dimensions_changed (rows, cols); - cell_grid.on_dimensions_changed (rows, cols); + private void on_game_state_changed () { + var gs = controller.game_state; + update_all_labels_completeness (); + // // Avoid updating header bar while generating otherwise generation will be cancelled. + // // Headerbar will update when generation finished. + // if (controller.game_state != GameState.GENERATING) { + restart_destructive = !model.is_blank (gs); + headerbar_factory.on_game_state_changed (gs); + + // update_header_bar (gs); + // } } + // public void on_dimensions_changed (uint rows, uint cols) { + // row_clue_box.on_dimensions_changed (rows, cols); + // column_clue_box.on_dimensions_changed (rows, cols); + // cell_grid.on_dimensions_changed (rows, cols); + // } + public string[] get_clues (bool is_column) { var label_box = is_column ? column_clue_box : row_clue_box; return label_box.get_clue_texts (); @@ -313,7 +331,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public void update_clues_from_string_array (string[] clues, bool is_column) { var clue_box = is_column ? column_clue_box : row_clue_box; - var lim = is_column ? controller.dimensions.width : controller.dimensions.height; + var lim = is_column ? controller.rows : controller.columns; for (int i = 0; i < lim; i++) { clue_box.update_clue_text (i, clues[i]); @@ -390,8 +408,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { // } // } - public void update_title () { - headerbar_factory.update_title (game_name, controller.current_game_path, game_grade); + public void update_title (string title) { + headerbar_factory.update_title (game_name, title, game_grade); } public void on_can_go_changed (bool forward, bool back) { @@ -425,11 +443,11 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } private void update_all_labels_completeness () { - for (int r = 0; r < controller.dimensions.height; r++) { + for (int r = 0; r < controller.rows; r++) { update_clue_complete (r, false); } - for (int c = 0; c < controller.dimensions.width; c++) { + for (int c = 0; c < controller.columns; c++) { update_clue_complete (c, true); } } diff --git a/src/widgets/Cellgrid.vala b/src/widgets/Cellgrid.vala index 183a699..b2cf621 100644 --- a/src/widgets/Cellgrid.vala +++ b/src/widgets/Cellgrid.vala @@ -65,7 +65,8 @@ public struct Gnonograms.Cell { public class Gnonograms.CellGrid : Gtk.DrawingArea { public signal void leave (); - public unowned View view { get; construct; } + // public unowned View view { get; construct; } + // public Model model { get; construct; } public Cell? current_cell { get; set; } public Cell? previous_cell { get; set; } public bool frozen { get; set; } @@ -119,18 +120,22 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { private My2DCellArray? array { get { - return view.model.display_data; + return model.display_data; } } + + private Controller controller = Controller.get_default (); + private Model model = Model.get_default (); - public CellGrid (View view) { - Object ( - view: view - ); - } + // private App app = ((App)(Application.get_default ())); + + // public CellGrid (Model model) { + // Object ( + // model: model + // ); + // } construct { - var app = ((Gnonograms.App)(Application.get_default ())); hexpand = true; vexpand = true; current_cell = null; @@ -150,10 +155,12 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { queue_draw (); }); - app.game_state_changed.connect (on_game_state_changed); - app.dimensions_changed.connect (on_dimensions_changed); + controller.notify["game-state"].connect (on_game_state_changed); + // app.bind_property ("game-state", this, "game-state"); + // app.game_state_changed.connect (on_game_state_changed); + controller.dimensions_changed.connect (on_dimensions_changed); - view.model.changed.connect (() => { + model.changed.connect (() => { if (!dirty) { dirty = true; queue_draw (); @@ -181,19 +188,19 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { colors[setting, (int) CellState.UNKNOWN].parse (Gnonograms.UNKNOWN_COLOR); colors[setting, (int) CellState.EMPTY].parse (settings.get_string ("empty-color")); colors[setting, (int) CellState.FILLED].parse (settings.get_string ("filled-color")); - update_colors (); + update_colors (controller.game_state); queue_draw (); return Source.REMOVE; }); } - private GameState gs; - private void on_game_state_changed (GameState gs) { - this.gs = gs; - update_colors (); + // private GameState gs; + private void on_game_state_changed () { + // this.gs = gs; + update_colors (controller.game_state); } - private void update_colors () { + private void update_colors (GameState gs) { unknown_color = colors[(int)gs, (int)CellState.UNKNOWN]; fill_color = colors[(int)gs, (int)CellState.FILLED]; empty_color = colors[(int)gs, (int)CellState.EMPTY]; diff --git a/src/widgets/Cluebox.vala b/src/widgets/Cluebox.vala index f990575..447f043 100644 --- a/src/widgets/Cluebox.vala +++ b/src/widgets/Cluebox.vala @@ -17,6 +17,8 @@ public class Gnonograms.ClueBox : Gtk.Widget { public Pango.FontDescription font_desc { get; set; } private Gee.ArrayList clues; + private Controller controller = Controller.get_default (); + public ClueBox (bool _holds_column_clues) { Object ( holds_column_clues: _holds_column_clues @@ -46,6 +48,7 @@ public class Gnonograms.ClueBox : Gtk.Widget { } notify["cell-size"].connect (update_size_request); + controller.dimensions_changed.connect (on_dimensions_changed); } private void update_size_request () { From ef85c73fe495609bc208e28230960a993f79a445 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Tue, 10 Dec 2024 19:06:25 +0000 Subject: [PATCH 127/142] Cleanup; handle not restored; disable settings temp --- src/Application.vala | 14 --- src/Controller.vala | 104 +++++------------ src/HeaderBar/AppPopover.vala | 4 +- src/HeaderBar/HeaderBarFactory.vala | 39 +------ src/Model.vala | 8 -- src/View.vala | 169 ++++++---------------------- src/misc/Constants.vala | 3 +- src/services/Filewriter.vala | 20 +--- src/services/History.vala | 52 ++------- 9 files changed, 83 insertions(+), 330 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index 07b4b34..6c7bc38 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -16,8 +16,6 @@ namespace Gnonograms { public const string ACTION_PREFIX = ACTION_GROUP + "."; public const string ACTION_UNDO = "action-undo"; public const string ACTION_REDO = "action-redo"; - // public const string ACTION_ZOOM_IN = "action-zoom-in"; - // public const string ACTION_ZOOM_OUT = "action-zoom-out"; public const string ACTION_CURSOR_UP = "action-cursor_up"; public const string ACTION_CURSOR_DOWN = "action-cursor_down"; public const string ACTION_CURSOR_LEFT = "action-cursor_left"; @@ -48,16 +46,9 @@ namespace Gnonograms { public GLib.Settings saved_state; public GLib.Settings settings; - - public class App : Gtk.Application { - // public GameState game_state { get; private set; default = LOAD_SAVE; } - private Controller controller; - // public signal void game_state_changed (GameState gs); - - public App () { Object ( application_id: Config.APP_ID, @@ -98,12 +89,7 @@ namespace Gnonograms { public override void activate () { if (controller == null) { controller = Controller.get_default (); - // controller.quit_app.connect (quit); add_window (controller.window); - // Only the controller should change the global game state. - // controller.notify["game-state"].connect (() => { - // game_state = controller.game_state; - // }); } else { controller.window.present (); } diff --git a/src/Controller.vala b/src/Controller.vala index 3d3de22..fa10f51 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -5,7 +5,11 @@ * Authored by: Jeremy Wootten */ + +public const string UNTITLED_NAME = N_("Untitled"); + public class Gnonograms.Controller : GLib.Object { + public static Controller get_default () { if (instance == null) { instance = new Controller (); @@ -22,12 +26,14 @@ public class Gnonograms.Controller : GLib.Object { // public signal void changed_dimensions (uint rows, uint cols); public Gtk.Window window { get { return (Gtk.Window)view;}} - public GameState game_state { get; private set; } - public uint rows { get; private set; } - public uint columns { get; private set; } - public Difficulty generator_grade { get; private set; } - public string game_name { get; private set; } - private string current_game_path { get; private set; default = ""; } + // synced with settings so writable + public GameState game_state { get; set; } + public Difficulty generator_grade { get; set; } // synced with settings + public uint rows { get; set; } + public uint columns { get; set; } + public string game_name { get; set; default = _(UNTITLED_NAME); } // Can be set be App Popover + public string current_game_path { get; set; default = ""; } + /* Any game that was not saved by this app is regarded as read only - any alterations * must be "Saved As" - which by default is writable. */ public bool is_readonly { get; private set; default = false;} @@ -36,18 +42,11 @@ public class Gnonograms.Controller : GLib.Object { return history.can_go_back; } } - // private set; } public bool can_go_forward { get { return history.can_go_forward; } } - - - // public bool is_solving { - // get { return game_state == SOLVING; } - // } - private View view; private Model model; private Solver? solver; @@ -67,35 +66,10 @@ public class Gnonograms.Controller : GLib.Object { } construct { - game_name = _(UNTITLED_NAME); - // model = new Model (this); - history = new History (); - - // app.game_state_changed.connect ((gs) => { - // if (gs != game_state) { - // game_state = gs; - // } - // }); - - notify["game-state"].connect (() => { - warning ("notify gamestate - now %s", game_state.to_string ()); - // if (game_state != GameState.LOAD_SAVE) { /* Do not clear on save */ - // clear_history (); - // } - - // if (game_state == GameState.GENERATING) { - // on_new_random_request (); - // } - }); - - notify["rows"].connect (on_dimensions_changed); notify["columns"].connect (on_dimensions_changed); - // notify["game-state"].connect (() => { - // app.game_state_changed (game_state); - // }); var data_home_folder_current = Path.build_path ( Path.DIR_SEPARATOR_S, @@ -121,16 +95,17 @@ public class Gnonograms.Controller : GLib.Object { Gnonograms.UNSAVED_FILENAME ); - saved_state.bind ("mode", this, "game-state", SettingsBindFlags.DEFAULT); - saved_state.bind ("current-game-path", this, "current-game-path", SettingsBindFlags.DEFAULT); - settings.bind ("grade", this, "generator-grade", SettingsBindFlags.DEFAULT); - settings.bind ("rows", this, "rows", SettingsBindFlags.DEFAULT); - settings.bind ("columns", this, "columns", SettingsBindFlags.DEFAULT); + // saved_state.bind ("mode", this, "game-state", SettingsBindFlags.DEFAULT); + // saved_state.bind ("current-game-path", this, "current-game-path", SettingsBindFlags.DEFAULT); + // settings.bind ("grade", this, "generator-grade", SettingsBindFlags.DEFAULT); + // settings.bind ("rows", this, "rows", SettingsBindFlags.DEFAULT); + // settings.bind ("columns", this, "columns", SettingsBindFlags.DEFAULT); + restore_game.begin ((obj, res) => { if (!restore_game.end (res)) { /* Error normally thrown if running without installing */ warning ("Restoring game failed"); - // restore_dimensions (); + restore_defaults (); new_game (); } }); @@ -169,9 +144,6 @@ public class Gnonograms.Controller : GLib.Object { BindingFlags.SYNC_CREATE ); - // notify["current-game-path"].connect (() => { - // view.update_title (game_name); - // }); notify["game-name"].connect (() => { view.update_title (game_name); }); @@ -181,6 +153,14 @@ public class Gnonograms.Controller : GLib.Object { }); } + private void restore_defaults () { + rows = 10; + columns = 15; + generator_grade = Difficulty.MODERATE; + view.game_grade = Difficulty.UNDEFINED; + game_state = GameState.SETTING; + + } private void on_dimensions_changed () { solver = new Solver (dimensions); @@ -197,7 +177,6 @@ public class Gnonograms.Controller : GLib.Object { } public void change_mode (GameState mode) { - warning ("change mode"); switch (mode) { case SETTING: case SOLVING: @@ -214,7 +193,6 @@ public class Gnonograms.Controller : GLib.Object { } public void prepare_quit () { -warning ("quit - hold"); if (solver != null) { solver.cancel (); } @@ -223,8 +201,6 @@ warning ("quit - hold"); app.hold (); save_game_state.begin ((obj, res) => { // Always quit for now - warning ("save game state end"); - warning ("release"); app.release (); app.quit (); }); @@ -243,7 +219,6 @@ warning ("quit - hold"); private void new_game () { clear (); - // app.game_state_changed (SETTING); game_state = GameState.SETTING; game_name = _(UNTITLED_NAME); } @@ -288,7 +263,6 @@ warning ("quit - hold"); } private async void save_game_state () { -warning ("save game state"); if (temporary_game_path != null) { try { var current_game_file = File.new_for_path (temporary_game_path); @@ -307,9 +281,7 @@ warning ("save game state"); } private async bool restore_game () { - warning ("restore game"); if (temporary_game_path != null) { - warning ("restore temp"); var current_game_file = File.new_for_path (temporary_game_path); return yield load_game_async (current_game_file); } else { @@ -318,7 +290,6 @@ warning ("save game state"); } private async string? write_game (string? path, bool save_state = false) { -warning ("Controller: write game. game state %s", game_state.to_string ()); Filewriter? file_writer = null; // app.game_state_changed (LOAD_SAVE); @@ -344,11 +315,8 @@ warning ("Controller: write game. game state %s", game_state.to_string ()); game_state = LOAD_SAVE; try { if (save_state) { - warning ("saving state"); yield file_writer.write_position_file (saved_games_folder, path, game_name); - warning ("after write position file"); } else { - warning ("saving game"); yield file_writer.write_game_file ( saved_games_folder, path, @@ -362,8 +330,6 @@ warning ("Controller: write game. game state %s", game_state.to_string ()); _("Unable to save %s").printf (basename), e.message ); - } else { - warning ("CANCELLED"); } return null; @@ -375,25 +341,19 @@ warning ("Controller: write game. game state %s", game_state.to_string ()); } public void load_game (File? game) { -warning ("Controller: load game"); load_game_async.begin (game, (obj, res) => { if (!load_game_async.end (res)) { - warning ("Load game failed"); new_or_random_game (); - } else { - warning ("loaded game. State %s", game_state.to_string ()); } }); } private async bool load_game_async (File? game) { -warning ("Controller: load game async"); Filereader? reader = null; clear_history (); reader = new Filereader (); var gs = game_state; game_state = LOAD_SAVE; - // app.game_state_changed (LOAD_SAVE); try { yield reader.read ( window, @@ -422,17 +382,7 @@ warning ("Controller: load game async"); } if (reader.valid && (yield load_common (reader))) { - warning ("Valid and loaded common"); - // if (reader.state != GameState.UNDEFINED) { - // game_state = reader.state; - warning ("reader state %s", reader.state.to_string ()); - // app.game_state_changed (reader.state); - // // } else { game_state = reader.state; - // } else { - // game_state = GameState.SOLVING; - // } - history.from_string (reader.moves); if (history.can_go_back) { view.make_move (history.get_current_move ()); diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 7ae9fd5..73f7561 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -14,7 +14,8 @@ public class Gnonograms.AppPopover : Gtk.Popover { placeholder_text = _("Enter title of game here"), margin_top = 12, }; - controller.bind_property ("game-name", title_entry, "text", BIDIRECTIONAL | SYNC_CREATE); + title_entry.bind_property ("text", controller, "game-name", BIDIRECTIONAL); + var grade_setting = new Gtk.DropDown.from_strings ( Difficulty.all_human ()); var grade_preference = new PreferenceRow (_("Degree of difficulty"), grade_setting); @@ -44,7 +45,6 @@ public class Gnonograms.AppPopover : Gtk.Popover { var column_preference = new PreferenceRow (_("Columns"), column_setting); //TODO Add Clue help switch - var load_game_button = new PopoverButton (_("Load"), ACTION_PREFIX + ACTION_OPEN); var save_game_button = new PopoverButton (_("Save"), ACTION_PREFIX + ACTION_SAVE); var save_as_game_button = new PopoverButton (_("Save to Different File"), ACTION_PREFIX + ACTION_SAVE_AS); diff --git a/src/HeaderBar/HeaderBarFactory.vala b/src/HeaderBar/HeaderBarFactory.vala index d52797f..f067d9f 100644 --- a/src/HeaderBar/HeaderBarFactory.vala +++ b/src/HeaderBar/HeaderBarFactory.vala @@ -22,7 +22,6 @@ public class Gnonograms.HeaderBarFactory : Object { private AppPopover app_popover; private Gtk.Button auto_solve_button; private Gtk.Button restart_button; - // private App app = (App)(Application.get_default ()); public HeaderBarFactory (Gnonograms.View view) { Object ( @@ -99,16 +98,12 @@ public class Gnonograms.HeaderBarFactory : Object { mode_switch.notify["active"].connect (() => { if (mode_switch.active) { - warning ("solving"); mode_switch.activate_action (ACTION_PREFIX + ACTION_SOLVING_MODE, null); } else { - warning ("setting"); mode_switch.activate_action (ACTION_PREFIX + ACTION_SETTING_MODE, null); } - // app.game_state_changed (mode_switch.active ? GameState.SOLVING : GameState.SETTING); - // controller.game_state = mode_switch.active ? GameState.SOLVING : GameState.SETTING; }); - + progress_indicator = new ProgressIndicator (); title_label = new Gtk.Label ("Gnonograms") { @@ -153,7 +148,6 @@ public class Gnonograms.HeaderBarFactory : Object { } public void on_game_state_changed (GameState gs) { -warning ("headerbar game state changed to %s", gs.to_string ()); if (gs == GENERATING) { generate_button.sensitive = false; return; @@ -168,14 +162,6 @@ warning ("headerbar game state changed to %s", gs.to_string ()); mode_switch.active = !is_setting; mode_switch.sensitive = sensitive; - // restart_button. - // undo_button.sensitive = sensitive && view.can_go_back; - // redo_button.sensitive = sensitive && view.can_go_forward; - // check_correct_button.sensitive = ( - // sensitive && - // gs == GameState.SOLVING && - // view.can_go_back - // ); hint_button.sensitive = sensitive && is_solving; auto_solve_button.sensitive = is_setting; @@ -184,26 +170,13 @@ warning ("headerbar game state changed to %s", gs.to_string ()); public void popdown_menus () { app_popover.popdown (); } - + public void on_can_go_changed (bool forward, bool back) { - // warning ("Headbar_factory: on can go changed"); check_correct_button.sensitive = back; undo_button.sensitive = back; redo_button.sensitive = forward; - -// - notify["can-go-back"].connect (() => { -// - check_correct_button.sensitive = can_go_back && -// - controller.game_state == GameState.SOLVING; -// - undo_button.sensitive = can_go_back; -// - /* May be destructive even if no history (e.g. after automatic solve) */ -// - restart_destructive |= can_go_back; -// - }); -// - -// - notify["can-go-forward"].connect (() => { -// - redo_button.sensitive = can_go_forward; -// - }); } - + public void update_title (string name, string path, Difficulty grade) { title_label.label = name; title_label.tooltip_text = path; @@ -213,11 +186,11 @@ warning ("headerbar game state changed to %s", gs.to_string ()); progress_stack.set_visible_child_name ("None"); } } - + public void show_working (string text) { progress_indicator.text = text; } - + public void hide_progress (Difficulty game_grade) { if (game_grade != Difficulty.UNDEFINED) { progress_stack.set_visible_child_name ("Title"); @@ -225,7 +198,7 @@ warning ("headerbar game state changed to %s", gs.to_string ()); progress_stack.set_visible_child_name ("None"); } } - + public void show_progress (Cancellable? cancellable) { progress_indicator.cancellable = cancellable; progress_stack.set_visible_child_name ("Progress"); diff --git a/src/Model.vala b/src/Model.vala index 5502b39..1e4ac0e 100644 --- a/src/Model.vala +++ b/src/Model.vala @@ -29,7 +29,6 @@ public class Gnonograms.Model : GLib.Object { } } - // public Controller controller { get; construct; } private Controller controller = Controller.get_default (); private My2DCellArray solution_data { get; set; } @@ -43,16 +42,10 @@ public class Gnonograms.Model : GLib.Object { } } - // public Model (Controller controller) { - // Object ( - // controller: controller - // ); - // } private Model () {} construct { make_data_arrays (); - // var app = (Gnonograms.App) Application.get_default (); controller.dimensions_changed.connect (on_dimensions_changed); controller.notify["game-state"].connect (() => { changed (); @@ -63,7 +56,6 @@ public class Gnonograms.Model : GLib.Object { this.rows = rows; this.cols = cols; make_data_arrays (); - // changed (); } private void make_data_arrays () { diff --git a/src/View.vala b/src/View.vala index 806c414..c03cbda 100644 --- a/src/View.vala +++ b/src/View.vala @@ -67,39 +67,18 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private Controller controller = Controller.get_default (); private Model model = Model.get_default (); - private ClueBox row_clue_box; private ClueBox column_clue_box; private CellGrid cell_grid; - private Gtk.MenuButton menu_button; - private HeaderBarFactory headerbar_factory; - // private Granite.ModeSwitch mode_switch; private Gtk.Grid main_grid; private Adw.ToastOverlay toast_overlay; - - - // private Gtk.Button generate_button; - // private Gtk.Button undo_button; - // private Gtk.Button redo_button; - // private Gtk.Button check_correct_button; - // private Gtk.Button hint_button; - // private AppPopover app_popover; - // private Gtk.Button auto_solve_button; - // private Gtk.Button restart_button; private uint drawing_with_key = 0; private CellState drawing_with_state = INVALID; private uint paint_fill_key = Gdk.keyval_from_name ("f"); private uint paint_empty_key = Gdk.keyval_from_name ("e"); private uint paint_unknown_key = Gdk.keyval_from_name ("x"); - // private App app = (App)(Application.get_default ()); - // public View (Model _model, Controller controller) { - // Object ( - // model: _model, - // controller: controller - // ); - // } private View () {} @@ -192,11 +171,16 @@ public class Gnonograms.View : Gtk.ApplicationWindow { main_grid.attach (column_clue_box, 1, 0, 1, 1); /* Clues for columns */ main_grid.attach (cell_grid, 1, 1, 1, 1); + toast_overlay = new Adw.ToastOverlay () { + child = main_grid + }; + + child = toast_overlay; + var key_controller = new Gtk.EventControllerKey (); main_grid.add_controller (key_controller); key_controller.key_pressed.connect ((keyval, keycode, state) => { - warning ("key pressed"); if (keyval == paint_fill_key) { paint_filled (); } else if (keyval == paint_empty_key) { @@ -211,7 +195,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { }); key_controller.key_released.connect ((keyval, keycode, state) => { - warning ("key released"); if (keyval == drawing_with_key) { stop_painting (); } @@ -221,7 +204,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { button_controller.set_button (0); // Listen to any button main_grid.add_controller (button_controller); button_controller.pressed.connect ((n_press, x, y) => { - // warning ("button press"); var button = button_controller.get_current_button (); var shift = (SHIFT_MASK in button_controller.get_current_event_state ()); var set_unknown = (n_press == 2 || button == Gdk.BUTTON_MIDDLE); @@ -239,18 +221,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { stop_painting (); }); - toast_overlay = new Adw.ToastOverlay () { - child = main_grid - }; - - child = toast_overlay; - - var flags = BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE; - // bind_property ( - // "restart-destructive", - // restart_button, "restart-destructive", - // BindingFlags.SYNC_CREATE - // ); current_cell = Cell () { row = 0, col = 0, state = UNKNOWN }; previous_cell = current_cell.clone (); @@ -266,22 +236,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { ); controller.notify["game-state"].connect (on_game_state_changed); - // app.game_state_changed.connect ((gs) => { - // controller.notify["game-state"].connect (() => { - - // }); - - // controller.notify["game-name"].connect (update_title); - // app.dimensions_changed.connect (on_dimensions_changed); - // notify["readonly"].connect (() => { - // save_game_button.sensitive = readonly; - // }); - // notify["game-grade"].connect (update_title); - // notify["can-go-back"].connect (on_can_go_changed); - // notify["can-go-forward"].connect (on_can_go_changed); notify["current-cell"].connect (() => { - // warning ("current cell changed"); highlight_labels (previous_cell, false); highlight_labels (current_cell, true); if (current_cell != null && @@ -308,21 +264,10 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private void on_game_state_changed () { var gs = controller.game_state; update_all_labels_completeness (); - // // Avoid updating header bar while generating otherwise generation will be cancelled. - // // Headerbar will update when generation finished. - // if (controller.game_state != GameState.GENERATING) { restart_destructive = !model.is_blank (gs); headerbar_factory.on_game_state_changed (gs); - - // update_header_bar (gs); - // } } - // public void on_dimensions_changed (uint rows, uint cols) { - // row_clue_box.on_dimensions_changed (rows, cols); - // column_clue_box.on_dimensions_changed (rows, cols); - // cell_grid.on_dimensions_changed (rows, cols); - // } public string[] get_clues (bool is_column) { var label_box = is_column ? column_clue_box : row_clue_box; @@ -357,9 +302,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } public void make_move (Move m) requires (m.is_valid ()) { - // if (!m.is_null ()) { update_current_and_model (m.cell.state, m.cell); - // } } public void send_notification (string text) { @@ -382,58 +325,17 @@ public class Gnonograms.View : Gtk.ApplicationWindow { headerbar_factory.hide_progress (game_grade); update_all_labels_completeness (); - // update_header_bar (); } - // private void update_header_bar () { - // var gs = controller.game_state; - // restart_destructive = !model.is_blank (gs); - // headerbar_factory.on_game_state_changed (gs); - // mode_switch.active = controller.game_state != GameState.SETTING; - - // switch (controller.game_state) { - // case GameState.SETTING: - // set_buttons_sensitive (true); - // break; - // case GameState.SOLVING: - // set_buttons_sensitive (true); - - // break; - // case GameState.GENERATING: - // set_buttons_sensitive (false); - - // break; - // default: - // break; - // } - // } - public void update_title (string title) { headerbar_factory.update_title (game_name, title, game_grade); } public void on_can_go_changed (bool forward, bool back) { - // warning ("on can go changed"); headerbar_factory.on_can_go_changed (forward, back); } - // private void set_buttons_sensitive (bool sensitive) { - // generate_button.sensitive = controller.game_state != GameState.GENERATING; - // mode_switch.sensitive = sensitive; - // restart_destructive = sensitive && !model.is_blank (controller.game_state); - // undo_button.sensitive = sensitive && can_go_back; - // redo_button.sensitive = sensitive && can_go_forward; - // check_correct_button.sensitive = - // sensitive && - // controller.game_state == GameState.SOLVING && - // can_go_back; - - // hint_button.sensitive = sensitive && controller.game_state == GameState.SOLVING; - // auto_solve_button.sensitive = controller.game_state == GameState.SETTING; - // } - private void highlight_labels (Cell? c, bool is_highlight) { - /* If c is NULL_CELL then will unhighlight all labels */ if (c == null) { return; } @@ -468,10 +370,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { CellState state = drawing_with_state, Cell? target = current_cell ) requires (target != null) { - // if (target == NULL_CELL) { - // return; - // } -warning ("make move at cell"); var prev_state = model.get_data_for_cell (target); var cell = update_current_and_model (state, target); @@ -703,46 +601,45 @@ warning ("make move at cell"); return; } -warning ("paint cell state"); drawing_with_state = cs; make_move_at_cell (); } // Code based largely on elementary Code app - private ulong color_scheme_listener_handler_id = 0; - private void update_style () { - var gtk_settings = Gtk.Settings.get_default (); - var granite_settings = Granite.Settings.get_default (); - var following_system = settings.get_boolean ("follow-system-style"); - disconnect_color_scheme_preference_listener (following_system); - if (following_system) { + private ulong color_scheme_listener_handler_id = 0; + private void update_style () { + var gtk_settings = Gtk.Settings.get_default (); + var granite_settings = Granite.Settings.get_default (); + var following_system = settings.get_boolean ("follow-system-style"); + disconnect_color_scheme_preference_listener (following_system); + if (following_system) { + gtk_settings.gtk_application_prefer_dark_theme = ( + granite_settings.prefers_color_scheme == DARK + ); + color_scheme_listener_handler_id = granite_settings.notify["prefers-color-scheme"].connect (() => { gtk_settings.gtk_application_prefer_dark_theme = ( granite_settings.prefers_color_scheme == DARK ); - color_scheme_listener_handler_id = granite_settings.notify["prefers-color-scheme"].connect (() => { - gtk_settings.gtk_application_prefer_dark_theme = ( - granite_settings.prefers_color_scheme == DARK - ); - }); - } else { + }); + } else { + gtk_settings.gtk_application_prefer_dark_theme = settings.get_boolean ("prefer-dark-style"); + color_scheme_listener_handler_id = settings.notify["prefers-dark-style"].connect (() => { gtk_settings.gtk_application_prefer_dark_theme = settings.get_boolean ("prefer-dark-style"); - color_scheme_listener_handler_id = settings.notify["prefers-dark-style"].connect (() => { - gtk_settings.gtk_application_prefer_dark_theme = settings.get_boolean ("prefer-dark-style"); - }); - } + }); } + } - private void disconnect_color_scheme_preference_listener (bool following_system) { - if (color_scheme_listener_handler_id != 0) { - if (following_system) { - var granite_settings = Granite.Settings.get_default (); - granite_settings.disconnect (color_scheme_listener_handler_id); - } else { - settings.disconnect (color_scheme_listener_handler_id); - } - - color_scheme_listener_handler_id = 0; + private void disconnect_color_scheme_preference_listener (bool following_system) { + if (color_scheme_listener_handler_id != 0) { + if (following_system) { + var granite_settings = Granite.Settings.get_default (); + granite_settings.disconnect (color_scheme_listener_handler_id); + } else { + settings.disconnect (color_scheme_listener_handler_id); } + + color_scheme_listener_handler_id = 0; } + } } diff --git a/src/misc/Constants.vala b/src/misc/Constants.vala index 34bfaa8..576c488 100644 --- a/src/misc/Constants.vala +++ b/src/misc/Constants.vala @@ -5,7 +5,6 @@ * Authored by: Jeremy Wootten */ namespace Gnonograms { - // public const Cell NULL_CELL = { uint.MAX, uint.MAX, CellState.UNDEFINED }; public const uint MAXSIZE = 54; // max number rows or columns public const uint MINSIZE = 5; // Change to 1 when debugging public const uint SIZESTEP = 5; // Change to 1 when debugging @@ -15,7 +14,7 @@ namespace Gnonograms { public const string BLANKLABELTEXT = N_("?"); public const string GAMEFILEEXTENSION = ".gno"; public const string UNSAVED_FILENAME = "Unsaved Game" + GAMEFILEEXTENSION; - public const string UNTITLED_NAME = N_("Untitled"); + public const string APP_NAME = "Gnonograms"; public const string SETTING_FILLED_COLOR = "#000000"; /* Elementary Black 900 */ public const string SETTING_EMPTY_COLOR = "#fafafa"; /* Elementary Silver 100 */ diff --git a/src/services/Filewriter.vala b/src/services/Filewriter.vala index dd989fc..e8ae846 100644 --- a/src/services/Filewriter.vala +++ b/src/services/Filewriter.vala @@ -23,7 +23,6 @@ public class Gnonograms.Filewriter : Object { public string license { get; set; default = "";} public bool is_readonly { get; set; default = true;} - private FileStream? stream; public Filewriter (Gtk.Window? parent, @@ -55,7 +54,6 @@ public class Gnonograms.Filewriter : Object { string? path = null, string? _name = null ) throws Error { - warning ("writer write game"); if (_name != null) { name = _name; } else { @@ -97,17 +95,13 @@ public class Gnonograms.Filewriter : Object { throw new IOError.CANCELLED ("File exists"); } -warning ("game path %s", game_path); /* @game_path is local path, not a uri */ stream = FileStream.open (game_path, "w"); -warning ("opened stream"); if (stream == null) { - warning ("stream is null"); throw new IOError.FAILED ("Could not open filestream to %s".printf (game_path)); } if (name == null || name.length == 0) { - warning ("No name"); throw new IOError.NOT_INITIALIZED ("No name to save"); } @@ -123,7 +117,6 @@ warning ("opened stream"); } if (rows == 0 || cols == 0) { - warning ("no dimensions"); throw new IOError.NOT_INITIALIZED ("No dimensions to save"); } @@ -132,12 +125,10 @@ warning ("opened stream"); stream.printf ("%u\n", cols); if (row_clues.length == 0 || col_clues.length == 0) { - warning ("no clues"); throw new IOError.NOT_INITIALIZED ("No clues to save"); } if (row_clues.length != rows || col_clues.length != cols) { - warning ("mismatch"); throw new IOError.NOT_INITIALIZED ("Clues do not match dimensions"); } @@ -151,11 +142,8 @@ warning ("opened stream"); stream.printf ("%s\n", s); } -warning ("flush"); stream.flush (); -warning ("solution is %s", solution != null ? "NOT null" : "null"); -warning ("save_solution is %s", save_solution.to_string ()); if (solution != null && save_solution) { stream.printf ("[Solution grid]\n"); stream.printf ("%s", solution.to_string ()); @@ -163,7 +151,6 @@ warning ("save_solution is %s", save_solution.to_string ()); stream.printf ("[Locked]\n"); stream.printf (is_readonly.to_string () + "\n"); -warning ("End of write game file"); } /*** Writes complete information to reload game state ***/ @@ -173,17 +160,15 @@ warning ("End of write game file"); string? name = null ) throws Error { if (working == null) { - warning ("No working grid"); throw (new IOError.NOT_INITIALIZED ("No working grid to save")); } -warning ("write position "); + yield write_game_file (save_dir_path, path, name ); -warning ("after write game file"); + stream.printf ("[Working grid]\n"); stream.printf (working.to_string ()); stream.printf ("[State]\n"); stream.printf (state.to_string () + "\n"); -warning ("write state %s", state.to_string ()); if (name != _(UNTITLED_NAME)) { stream.printf ("[Original path]\n"); stream.printf (game_path.to_string () + "\n"); @@ -195,6 +180,5 @@ warning ("write state %s", state.to_string ()); } stream.flush (); -warning ("end of write position"); } } diff --git a/src/services/History.vala b/src/services/History.vala index 49c35e9..4256b22 100644 --- a/src/services/History.vala +++ b/src/services/History.vala @@ -6,12 +6,11 @@ */ public class Gnonograms.History : GLib.Object { private class HistoryStack : Object { - public bool empty { + public bool empty { get { return stack.is_empty; } } - // private set; } private Gee.Deque stack; @@ -21,31 +20,18 @@ public class Gnonograms.History : GLib.Object { public void push_move (Move mv) requires (mv.is_valid ()) { stack.offer_head (mv); - // empty = false; } public Move? peek_move () { - // if (empty) { - // return null; - // } else { - return stack.peek_head (); - // } + return stack.peek_head (); } public Move? pop_move () { return stack.poll_head (); - // Move mv; - // if (!empty) { - // mv = - // } - - // empty = stack.is_empty; - // return mv; } public void clear () { stack.clear (); - // empty = true; } public string to_string () { @@ -59,41 +45,32 @@ public class Gnonograms.History : GLib.Object { } } - public bool can_go_back { + public bool can_go_back { get { return !back_stack.empty; } } - // ; private set; } - public bool can_go_forward { + + public bool can_go_forward { get { return !forward_stack.empty; } } - // private set; } private HistoryStack back_stack; private HistoryStack forward_stack; - + public signal void can_go_changed (bool forward, bool back); construct { back_stack = new HistoryStack (); forward_stack = new HistoryStack (); - - // back_stack.notify["empty"].connect (() => { - // can_go_back = !back_stack.empty; - // }); - - // forward_stack.notify["empty"].connect (() => { - // can_go_forward = !forward_stack.empty; - // }); } private void signal_can_go_changed () { can_go_changed (!forward_stack.empty, !back_stack.empty ); } - + public void clear_all () { forward_stack.clear (); back_stack.clear (); @@ -101,22 +78,17 @@ public class Gnonograms.History : GLib.Object { } public void record_move (Cell? cell, CellState previous_state) { -warning ("record move"); if (cell == null) { - warning ("IGNORE null cell"); return; } var new_move = new Gnonograms.Move.from_cell (cell, previous_state); - // if (new_move.cell.state != CellState.UNDEFINED) { - Move? last_move = back_stack.peek_move (); - if (new_move.equal (last_move)) { - warning ("ignore same as last move"); - return; - } + Move? last_move = back_stack.peek_move (); + if (new_move.equal (last_move)) { + return; + } - forward_stack.clear (); - // } + forward_stack.clear (); back_stack.push_move (new_move); signal_can_go_changed (); From 680f2c77499f5d3105798c77123c7c8e64e14300 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 11 Dec 2024 10:31:16 +0000 Subject: [PATCH 128/142] Fix sync AppPopover rows, cols, grade --- src/Controller.vala | 7 +++---- src/HeaderBar/AppPopover.vala | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index fa10f51..68e2218 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -95,9 +95,9 @@ public class Gnonograms.Controller : GLib.Object { Gnonograms.UNSAVED_FILENAME ); - // saved_state.bind ("mode", this, "game-state", SettingsBindFlags.DEFAULT); - // saved_state.bind ("current-game-path", this, "current-game-path", SettingsBindFlags.DEFAULT); - // settings.bind ("grade", this, "generator-grade", SettingsBindFlags.DEFAULT); + saved_state.bind ("mode", this, "game-state", SettingsBindFlags.DEFAULT); + saved_state.bind ("current-game-path", this, "current-game-path", SettingsBindFlags.DEFAULT); + settings.bind ("grade", this, "generator-grade", SettingsBindFlags.DEFAULT); // settings.bind ("rows", this, "rows", SettingsBindFlags.DEFAULT); // settings.bind ("columns", this, "columns", SettingsBindFlags.DEFAULT); @@ -156,7 +156,6 @@ public class Gnonograms.Controller : GLib.Object { private void restore_defaults () { rows = 10; columns = 15; - generator_grade = Difficulty.MODERATE; view.game_grade = Difficulty.UNDEFINED; game_state = GameState.SETTING; diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 73f7561..9bf4eb4 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -71,12 +71,12 @@ public class Gnonograms.AppPopover : Gtk.Popover { child = settings_box; - settings.bind ("columns", column_setting, "value", DEFAULT); - settings.bind ("rows", row_setting, "value", DEFAULT); + controller.bind_property ("columns", column_setting, "value", BIDIRECTIONAL); + controller.bind_property ("rows", row_setting, "value", BIDIRECTIONAL); - grade_setting.selected = settings.get_enum ("grade"); + grade_setting.selected = controller.generator_grade; grade_setting.notify["selected"].connect (() => { - settings.set_enum ("grade", (Difficulty)(grade_setting.selected)); + controller.generator_grade = (Difficulty)(grade_setting.selected); }); } } From 87017ada6e0b23ff7771535620b7a02df617630b Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 11 Dec 2024 16:28:24 +0000 Subject: [PATCH 129/142] Fix and simplify file writing --- src/Controller.vala | 95 ++++++++++++++++++------------------ src/services/Filereader.vala | 3 +- src/services/Filewriter.vala | 75 +++++++++++++--------------- 3 files changed, 84 insertions(+), 89 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index 68e2218..02b5720 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -26,17 +26,16 @@ public class Gnonograms.Controller : GLib.Object { // public signal void changed_dimensions (uint rows, uint cols); public Gtk.Window window { get { return (Gtk.Window)view;}} - // synced with settings so writable - public GameState game_state { get; set; } + public GameState game_state { get; set; } // Stored with game file public Difficulty generator_grade { get; set; } // synced with settings - public uint rows { get; set; } - public uint columns { get; set; } + public uint rows { get; set; } // Stored with game file. Can be set be App Popover + public uint columns { get; set; } // Stored with game file. Can be set be App Popover public string game_name { get; set; default = _(UNTITLED_NAME); } // Can be set be App Popover - public string current_game_path { get; set; default = ""; } - + public string current_game_path { get; set; default = ""; } // synced with settings /* Any game that was not saved by this app is regarded as read only - any alterations * must be "Saved As" - which by default is writable. */ - public bool is_readonly { get; private set; default = false;} + public bool is_readonly { get; private set; default = false;} // Stored with game file. + public bool can_go_back { get { return history.can_go_back; @@ -52,19 +51,16 @@ public class Gnonograms.Controller : GLib.Object { private Solver? solver; private SimpleRandomGameGenerator? generator; private Gnonograms.History history; - private string saved_games_folder; - private string? temporary_game_path = null; + private string temporary_game_path; private Gnonograms.App app = (Gnonograms.App) (Application.get_default ()); - - private Controller () {} - private Dimensions dimensions { get { return { columns, rows }; } } + private Controller () {} construct { history = new History (); @@ -199,7 +195,11 @@ public class Gnonograms.Controller : GLib.Object { if (generator == null) { app.hold (); save_game_state.begin ((obj, res) => { + if (!save_game_state.end (res)) { + critical ("Error saving game state"); + } // Always quit for now + warning ("quitting after save state"); app.release (); app.quit (); }); @@ -213,7 +213,7 @@ public class Gnonograms.Controller : GLib.Object { model.clear (); view.update_clues_from_solution (); clear_history (); - is_readonly = true; // Force Save As when saving new design + is_readonly = false; } private void new_game () { @@ -261,7 +261,10 @@ public class Gnonograms.Controller : GLib.Object { }); } - private async void save_game_state () { + // Always saved to temp file, not original + private async bool save_game_state () { + warning ("save game state"); + string? saved_file_path = null; if (temporary_game_path != null) { try { var current_game_file = File.new_for_path (temporary_game_path); @@ -272,11 +275,11 @@ public class Gnonograms.Controller : GLib.Object { } finally { warning ("writing unsaved game to %s", temporary_game_path); /* Save solution and current state */ - yield write_game (temporary_game_path, true); + saved_file_path = yield write_game (temporary_game_path, true); } - } else { - warning ("No temporary game path"); } + + return saved_file_path != null; } private async bool restore_game () { @@ -288,38 +291,34 @@ public class Gnonograms.Controller : GLib.Object { } } - private async string? write_game (string? path, bool save_state = false) { - Filewriter? file_writer = null; - - // app.game_state_changed (LOAD_SAVE); - file_writer = new Filewriter ( + private async string? write_game (string? path, bool save_state = false) requires (saved_games_folder != null) { + warning ("write game to %s, save state %s, save dir %s", path, save_state.to_string (), saved_games_folder); + var file_writer = new Filewriter ( window, dimensions, view.get_clues (false), view.get_clues (true), - history, - !model.solution_is_blank () + view.game_grade, + saved_games_folder, + path, + game_name + ) { - difficulty = view.game_grade, - state = game_state, - working = model.copy_working_data () + solution = !model.solution_is_blank () ? model.copy_solution_data () : null }; - if (file_writer.save_solution) { - file_writer.solution = model.copy_solution_data (); - } - - file_writer.is_readonly = is_readonly; var gs = game_state; game_state = LOAD_SAVE; try { if (save_state) { - yield file_writer.write_position_file (saved_games_folder, path, game_name); + yield file_writer.write_position_file ( + model.copy_working_data (), + gs, + history + ); } else { yield file_writer.write_game_file ( - saved_games_folder, - path, - game_name + is_readonly ); } } catch (Error e) { @@ -351,7 +350,6 @@ public class Gnonograms.Controller : GLib.Object { Filereader? reader = null; clear_history (); reader = new Filereader (); - var gs = game_state; game_state = LOAD_SAVE; try { yield reader.read ( @@ -376,16 +374,23 @@ public class Gnonograms.Controller : GLib.Object { } } - game_state = gs; + game_state = SOLVING; // Default to solving to hide solution return false; } if (reader.valid && (yield load_common (reader))) { - game_state = reader.state; - history.from_string (reader.moves); - if (history.can_go_back) { - view.make_move (history.get_current_move ()); + if (reader.has_working) { + model.set_working_data_from_string_array (reader.working[0 : dimensions.height]); } + + if (reader.has_state) { + game_state = reader.state; + history.from_string (reader.moves); + if (history.can_go_back) { + view.make_move (history.get_current_move ()); + } + } + } else { view.send_notification (_("Unable to load game. %s").printf (reader.err_msg)); return false; @@ -434,10 +439,6 @@ public class Gnonograms.Controller : GLib.Object { game_name = reader.name; } - if (reader.has_working) { - model.set_working_data_from_string_array (reader.working[0 : dimensions.height]); - } - return Source.REMOVE; }); @@ -547,7 +548,7 @@ public class Gnonograms.Controller : GLib.Object { warning ("save game as"); yield save_game_as (); } else { - warning ("write game"); + warning ("write game - no state"); var path = yield write_game (current_game_path, false); if (path != null && path != "") { current_game_path = path; diff --git a/src/services/Filereader.vala b/src/services/Filereader.vala index 5702746..4047cdd 100644 --- a/src/services/Filereader.vala +++ b/src/services/Filereader.vala @@ -282,13 +282,14 @@ public class Gnonograms.Filereader : Object { private bool get_gnonogram_state (string? body) { /* Default to SOLVING state to avoid inadvertently showing solution */ - state = GameState.SOLVING; + has_state = false; if (body != null) { string[] s = Utils.remove_blank_lines (body.split ("\n")); if (s != null && s.length == 1) { var state_string = s[0]; if (state_string.up ().contains ("SETTING")) { state = GameState.SETTING; + has_state = true; } } } diff --git a/src/services/Filewriter.vala b/src/services/Filewriter.vala index e8ae846..f71bee8 100644 --- a/src/services/Filewriter.vala +++ b/src/services/Filewriter.vala @@ -6,31 +6,31 @@ */ public class Gnonograms.Filewriter : Object { public DateTime date { get; construct; } - public History? history { get; construct; } public Gtk.Window? parent { get; construct; } public Difficulty difficulty { get; set; default = Difficulty.UNDEFINED;} - public GameState state { get; set; } public My2DCellArray? solution { get; set; default = null;} - public My2DCellArray? working { get; set; default = null;} public uint rows { get; construct; } public uint cols { get; construct; } public string name { get; set; } public string[] row_clues { get; construct; } public string[] col_clues { get; construct; } - public bool save_solution { get; construct; } - public string? game_path { get; private set; } + public string? game_path { get; set construct; } + public string? game_name { get; set construct; } + public string? save_dir_path { get; construct; } public string author { get; set; default = "";} public string license { get; set; default = "";} - public bool is_readonly { get; set; default = true;} - private FileStream? stream; - public Filewriter (Gtk.Window? parent, - Dimensions dimensions, - string[] row_clues, - string[] col_clues, - History? history, - bool save_solution) { + public Filewriter ( + Gtk.Window? parent, + Dimensions dimensions, + string[] row_clues, + string[] col_clues, + Difficulty difficulty, + string? save_dir_path, + string? game_path, + string game_name + ) { Object ( name: _(UNTITLED_NAME), @@ -39,8 +39,10 @@ public class Gnonograms.Filewriter : Object { cols: dimensions.width, row_clues: row_clues, col_clues: col_clues, - history: history, - save_solution: save_solution + difficulty: difficulty, + save_dir_path: save_dir_path, + game_path: game_path, + game_name: game_name ); } @@ -49,31 +51,24 @@ public class Gnonograms.Filewriter : Object { } /*** Writes minimum information required for valid game file ***/ - public async void write_game_file ( - string? save_dir_path = null, - string? path = null, - string? _name = null - ) throws Error { - if (_name != null) { - name = _name; - } else { - name = _(UNTITLED_NAME); + public async void write_game_file (bool is_readonly) throws Error { + if (game_name == null) { + game_name = _(UNTITLED_NAME); } - if (path == null || path.length <= 4) { - var game_file = yield Utils.get_open_save_file (parent, + if (game_path == null || game_path.length <= 4) { + var game_file = yield Utils.get_open_save_file ( + parent, _("Name and save this puzzle"), true, save_dir_path, - name + game_name ); if (game_file != null) { game_path = game_file.get_path (); } - } else { - game_path = path; - } + } if (game_path != null && (game_path.length < 4 || @@ -107,7 +102,7 @@ public class Gnonograms.Filewriter : Object { stream.printf ("[Description]\n"); stream.printf ("%s\n", name); - stream.printf ("%s\n", author); + stream.printf ("%s\n", author != "" ? author : "Gnonograms Generator"); stream.printf ("%s\n", date.to_string ()); stream.printf ("%u\n", difficulty); @@ -144,7 +139,7 @@ public class Gnonograms.Filewriter : Object { stream.flush (); - if (solution != null && save_solution) { + if (solution != null) { stream.printf ("[Solution grid]\n"); stream.printf ("%s", solution.to_string ()); } @@ -155,21 +150,19 @@ public class Gnonograms.Filewriter : Object { /*** Writes complete information to reload game state ***/ public async void write_position_file ( - string? save_dir_path = null, - string? path = null, - string? name = null + My2DCellArray working, + GameState state, + History history ) throws Error { - if (working == null) { - throw (new IOError.NOT_INITIALIZED ("No working grid to save")); - } - - yield write_game_file (save_dir_path, path, name ); +warning ("writing game file"); + yield write_game_file ( false ); +warning ("Writing working grid"); stream.printf ("[Working grid]\n"); stream.printf (working.to_string ()); stream.printf ("[State]\n"); stream.printf (state.to_string () + "\n"); - if (name != _(UNTITLED_NAME)) { + if (game_name != _(UNTITLED_NAME)) { stream.printf ("[Original path]\n"); stream.printf (game_path.to_string () + "\n"); } From d979e4dce3dc00b6613f66bef2d6d99bcb831de6 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 11 Dec 2024 16:28:51 +0000 Subject: [PATCH 130/142] Start to document file format --- FileFormat.txt | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 FileFormat.txt diff --git a/FileFormat.txt b/FileFormat.txt new file mode 100644 index 0000000..fce3ae4 --- /dev/null +++ b/FileFormat.txt @@ -0,0 +1,63 @@ +[Description] +Random pattern // Title of game to show in headerbar +Gnonograms Generator // Author (default) +2024-12-11T10:36:56+0000 // Date game was saved +2 // Difficulty 0 = Easy, 1 = Moderate, 2 = Difficult, 3 = Challenging, 4 = Advanced, 5 = Ambiguous +// [License] (unused) +[Dimensions] +10 // Rows +10 // Columns +[Row clues] +1, 4 +4 +4 +1, 2 +1, 3, 1 +2 +5 +4, 3 +2, 3 +2, 2 +[Column clues] +1, 2, 2 +1, 2 +1, 1, 1 +1, 1, 3 +6 +1, 3 +2, 1, 1 +2, 1, 2 +2, 1, 2 +2, 1, 1 +[Solution grid] // 0 = Unknown, 1 = Empty, 2 = Filled (optional) +2 1 1 1 1 1 2 2 2 2 +1 1 1 1 1 1 2 2 2 2 +2 2 2 2 1 1 1 1 1 1 +2 1 1 1 1 1 1 2 2 1 +1 1 2 1 2 2 2 1 1 2 +1 1 1 2 2 1 1 1 1 1 +1 1 1 1 2 2 2 2 2 1 +1 1 2 2 2 2 1 2 2 2 +2 2 1 2 2 2 1 1 1 1 +2 2 1 2 2 1 1 1 1 1 +[Locked] // Whether game is read-only (modifications must be saved to different file +true +// Following only saved to temp file for restoring on startup +[Working grid] +2 0 0 0 0 0 0 0 0 0 +0 2 0 0 0 0 0 0 0 0 +0 0 2 0 0 0 0 0 0 0 +0 0 0 2 0 0 0 0 0 0 +0 0 0 0 2 0 0 0 0 0 +0 0 0 0 0 2 0 0 0 0 +0 0 0 0 0 2 2 0 0 0 +0 0 0 0 0 0 2 2 0 0 +0 0 0 0 0 0 0 2 2 0 +0 0 0 0 0 0 0 0 2 2 +[State] // 0 = Setting 1 = Solving +GNONOGRAMS_GAME_STATE_SOLVING +[Original path] // Path to original game +/home/jeremy/.config/unsaved/Unsaved Game.gno +[History] // Series of moves made when solving [row, col, current_state, previous_state;] +0,0,2,0;1,1,2,0;2,2,2,0;3,3,2,0;4,4,2,0;5,5,2,0;6,5,2,0;6,6,2,0;7,6,2,0;7,7,2,0;8,7,2,0;8,8,2,0;9,8,2,0;9,9,2,0; + From 6b876bb091a57d865fc01be5a8a3302556e28c36 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 11 Dec 2024 16:54:42 +0000 Subject: [PATCH 131/142] Fix file reading --- src/Controller.vala | 16 +++++++++++++--- src/services/Filereader.vala | 6 ++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index 02b5720..a25627c 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -285,6 +285,7 @@ public class Gnonograms.Controller : GLib.Object { private async bool restore_game () { if (temporary_game_path != null) { var current_game_file = File.new_for_path (temporary_game_path); + warning ("restore game %s", current_game_file.get_path ()); return yield load_game_async (current_game_file); } else { return false; @@ -377,20 +378,24 @@ public class Gnonograms.Controller : GLib.Object { game_state = SOLVING; // Default to solving to hide solution return false; } - +warning ("reader read OK"); if (reader.valid && (yield load_common (reader))) { +warning ("load common OK"); if (reader.has_working) { +warning ("has working"); model.set_working_data_from_string_array (reader.working[0 : dimensions.height]); } if (reader.has_state) { +warning ("has state %s", reader.state.to_string ()); game_state = reader.state; history.from_string (reader.moves); if (history.can_go_back) { view.make_move (history.get_current_move ()); } + } else { + game_state = SOLVING; } - } else { view.send_notification (_("Unable to load game. %s").printf (reader.err_msg)); return false; @@ -419,6 +424,7 @@ public class Gnonograms.Controller : GLib.Object { Idle.add (() => { // Need time for model to update dimensions through notify signal model.blank_working (); // Do not reveal solution on load + model.blank_solution (); // Do not reveal solution on load if (reader.has_solution) { view.game_grade = reader.difficulty; @@ -431,6 +437,7 @@ public class Gnonograms.Controller : GLib.Object { } if (reader.has_solution) { + warning ("has solution"); model.set_solution_data_from_string_array (reader.solution[0 : dimensions.height]); view.update_clues_from_solution (); /* Ensure completeness correctly set */ } @@ -439,9 +446,11 @@ public class Gnonograms.Controller : GLib.Object { game_name = reader.name; } + load_common.callback (); return Source.REMOVE; }); + yield; is_readonly = reader.is_readonly; if (reader.original_path != null && reader.original_path != "") { current_game_path = reader.original_path; @@ -449,6 +458,7 @@ public class Gnonograms.Controller : GLib.Object { current_game_path = reader.game_file.get_path (); } +warning ("current game path now %s", current_game_path); return true; } @@ -573,7 +583,7 @@ public class Gnonograms.Controller : GLib.Object { } public void open_game () { - warning ("Controller: open game"); + warning ("Controller: open game (null)"); load_game_async.begin (null); /* Filereader will request load location */ } diff --git a/src/services/Filereader.vala b/src/services/Filereader.vala index 4047cdd..b53e521 100644 --- a/src/services/Filereader.vala +++ b/src/services/Filereader.vala @@ -286,10 +286,12 @@ public class Gnonograms.Filereader : Object { if (body != null) { string[] s = Utils.remove_blank_lines (body.split ("\n")); if (s != null && s.length == 1) { + has_state = true; var state_string = s[0]; if (state_string.up ().contains ("SETTING")) { - state = GameState.SETTING; - has_state = true; + state = SETTING; + } else { + state = SOLVING; } } } From b02edea17a4d6888f8e0f9b618541b486ed6c538 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 11 Dec 2024 18:12:48 +0000 Subject: [PATCH 132/142] Fix more bugs with loading and saving --- src/Controller.vala | 115 +++++++++++++++------------- src/HeaderBar/HeaderBarFactory.vala | 20 ++--- src/View.vala | 13 ++-- src/services/Filewriter.vala | 55 ++++++------- 4 files changed, 108 insertions(+), 95 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index a25627c..c26db94 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -140,10 +140,6 @@ public class Gnonograms.Controller : GLib.Object { BindingFlags.SYNC_CREATE ); - notify["game-name"].connect (() => { - view.update_title (game_name); - }); - history.can_go_changed.connect ((forward, back) => { view.on_can_go_changed (forward, back); }); @@ -282,6 +278,33 @@ public class Gnonograms.Controller : GLib.Object { return saved_file_path != null; } + // Called by save action + public async void save_game () { + warning ("Controller: save game"); + if (current_game_path == "") { + yield save_game_as (); + } else { + warning ("write game - no state"); + var path = yield write_game (current_game_path, false); + if (path != null && path != "") { + current_game_path = path; + notify_saved (path); + } + } + } + + // Called by save_as action + public async void save_game_as () { + warning ("Controller: save game as"); + /* Filewriter will request save location, no solution saved as default */ + var path = yield write_game (null, false); + if (path != null) { + current_game_path = path; + notify_saved (path); + is_readonly = false; + } + } + private async bool restore_game () { if (temporary_game_path != null) { var current_game_file = File.new_for_path (temporary_game_path); @@ -292,8 +315,12 @@ public class Gnonograms.Controller : GLib.Object { } } - private async string? write_game (string? path, bool save_state = false) requires (saved_games_folder != null) { - warning ("write game to %s, save state %s, save dir %s", path, save_state.to_string (), saved_games_folder); + private async string? write_game ( + string? save_to_path, + bool save_state = false + ) requires (saved_games_folder != null) { + + warning ("write game to %s, save state %s, save dir %s", save_to_path, save_state.to_string (), saved_games_folder); var file_writer = new Filewriter ( window, dimensions, @@ -301,8 +328,9 @@ public class Gnonograms.Controller : GLib.Object { view.get_clues (true), view.game_grade, saved_games_folder, - path, - game_name + current_game_path, + game_name, + save_to_path ) { solution = !model.solution_is_blank () ? model.copy_solution_data () : null @@ -359,6 +387,7 @@ public class Gnonograms.Controller : GLib.Object { game ); } catch (GLib.Error e) { + warning ("error on reading file %s", e.message); if (!(e is IOError.CANCELLED)) { var basename = game != null ? game.get_basename () : _("game"); var game_path = ""; @@ -405,6 +434,9 @@ warning ("has state %s", reader.state.to_string ()); } private async bool load_common (Filereader reader) { + view.game_grade = reader.difficulty; + is_readonly = reader.is_readonly; + if (reader.has_dimensions) { if (reader.rows > MAXSIZE || reader.cols > MAXSIZE) { reader.err_msg = (_("Dimensions too large")); @@ -413,6 +445,7 @@ warning ("has state %s", reader.state.to_string ()); reader.err_msg = (_("Dimensions too small")); return false; } else { + // This will resize model and view as well columns = reader.cols; rows = reader.rows; } @@ -421,42 +454,42 @@ warning ("has state %s", reader.state.to_string ()); return false; } + if (reader.has_row_clues && reader.has_col_clues) { + view.update_clues_from_string_array (reader.row_clues, false); + view.update_clues_from_string_array (reader.col_clues, true); + } else { + reader.err_msg = (_("Clues missing")); + return false; + } + + if (reader.name.length > 1 && reader.name != "") { + game_name = reader.name; + } + + + if (reader.original_path != null && reader.original_path != "") { + warning ("has original path %s", reader.original_path); + current_game_path = reader.original_path; + } else { + current_game_path = reader.game_file.get_path (); + } Idle.add (() => { // Need time for model to update dimensions through notify signal model.blank_working (); // Do not reveal solution on load model.blank_solution (); // Do not reveal solution on load - if (reader.has_solution) { - view.game_grade = reader.difficulty; - } else if (reader.has_row_clues && reader.has_col_clues) { - view.update_clues_from_string_array (reader.row_clues, false); - view.update_clues_from_string_array (reader.col_clues, true); - } else { - reader.err_msg = (_("Clues missing")); - return false; - } - if (reader.has_solution) { warning ("has solution"); model.set_solution_data_from_string_array (reader.solution[0 : dimensions.height]); view.update_clues_from_solution (); /* Ensure completeness correctly set */ } - if (reader.name.length > 1 && reader.name != "") { - game_name = reader.name; - } - load_common.callback (); return Source.REMOVE; }); yield; - is_readonly = reader.is_readonly; - if (reader.original_path != null && reader.original_path != "") { - current_game_path = reader.original_path; - } else { - current_game_path = reader.game_file.get_path (); - } + warning ("current game path now %s", current_game_path); return true; @@ -552,32 +585,6 @@ warning ("current game path now %s", current_game_path); return true; } - public async void save_game () { - warning ("Controller: save game"); - if (is_readonly || current_game_path == "") { - warning ("save game as"); - yield save_game_as (); - } else { - warning ("write game - no state"); - var path = yield write_game (current_game_path, false); - if (path != null && path != "") { - current_game_path = path; - notify_saved (path); - } - } - } - - public async void save_game_as () { - warning ("Controller: save game as"); - /* Filewriter will request save location, no solution saved as default */ - var path = yield write_game (null, false); - if (path != null) { - current_game_path = path; - notify_saved (path); - is_readonly = false; - } - } - private void notify_saved (string path) { view.send_notification (_("Saved to %s").printf (path)); } diff --git a/src/HeaderBar/HeaderBarFactory.vala b/src/HeaderBar/HeaderBarFactory.vala index f067d9f..6c44ec7 100644 --- a/src/HeaderBar/HeaderBarFactory.vala +++ b/src/HeaderBar/HeaderBarFactory.vala @@ -117,7 +117,7 @@ public class Gnonograms.HeaderBarFactory : Object { }; progress_stack.add_named (progress_indicator, "Progress"); progress_stack.add_named (title_label, "Title"); - progress_stack.add_named (new Gtk.Label (""), "None"); + // progress_stack.add_named (new Gtk.Label (""), "None"); progress_stack.set_visible_child_name ("Title"); header_bar = new Gtk.HeaderBar () { @@ -180,11 +180,13 @@ public class Gnonograms.HeaderBarFactory : Object { public void update_title (string name, string path, Difficulty grade) { title_label.label = name; title_label.tooltip_text = path; - if (grade != UNDEFINED) { + // if (grade != UNDEFINED) { + // warning ("show TITLE"); progress_stack.set_visible_child_name ("Title"); - } else { - progress_stack.set_visible_child_name ("None"); - } + // } else { + // warning ("show NONE"); + // progress_stack.set_visible_child_name ("None"); + // } } public void show_working (string text) { @@ -192,11 +194,11 @@ public class Gnonograms.HeaderBarFactory : Object { } public void hide_progress (Difficulty game_grade) { - if (game_grade != Difficulty.UNDEFINED) { + // if (game_grade != Difficulty.UNDEFINED) { progress_stack.set_visible_child_name ("Title"); - } else { - progress_stack.set_visible_child_name ("None"); - } + // } else { + // progress_stack.set_visible_child_name ("None"); + // } } public void show_progress (Cancellable? cancellable) { diff --git a/src/View.vala b/src/View.vala index c03cbda..e4a0c4b 100644 --- a/src/View.vala +++ b/src/View.vala @@ -52,16 +52,13 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public signal void debug_request (uint idx, bool is_column); #endif - // public Model model { get; construct; } - // public Controller controller { get; construct; } - public SimpleActionGroup view_actions { get; construct; } public Cell? current_cell { get; set; } public Cell? previous_cell { get; set; } public Difficulty generator_grade { get; set; } public Difficulty game_grade { get; set; } - public string game_name { get { return controller.game_name; } } + // public string game_name { get { return controller.game_name; } } public bool readonly { get; set; default = false;} public bool restart_destructive { get; set; default = false;} @@ -236,6 +233,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { ); controller.notify["game-state"].connect (on_game_state_changed); + controller.notify["game-name"].connect (update_title); + controller.notify["current-game-path"].connect (update_title); + notify["game-grade"].connect (update_title); notify["current-cell"].connect (() => { highlight_labels (previous_cell, false); @@ -327,8 +327,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { update_all_labels_completeness (); } - public void update_title (string title) { - headerbar_factory.update_title (game_name, title, game_grade); + public void update_title () { + warning ("View: update title %s", controller.game_name); + headerbar_factory.update_title (controller.game_name, controller.current_game_path, game_grade); } public void on_can_go_changed (bool forward, bool back) { diff --git a/src/services/Filewriter.vala b/src/services/Filewriter.vala index f71bee8..6408603 100644 --- a/src/services/Filewriter.vala +++ b/src/services/Filewriter.vala @@ -11,10 +11,11 @@ public class Gnonograms.Filewriter : Object { public My2DCellArray? solution { get; set; default = null;} public uint rows { get; construct; } public uint cols { get; construct; } - public string name { get; set; } + // public string name { get; set; } public string[] row_clues { get; construct; } public string[] col_clues { get; construct; } public string? game_path { get; set construct; } + public string? save_to_path { get; set construct; } public string? game_name { get; set construct; } public string? save_dir_path { get; construct; } public string author { get; set; default = "";} @@ -29,11 +30,11 @@ public class Gnonograms.Filewriter : Object { Difficulty difficulty, string? save_dir_path, string? game_path, - string game_name + string game_name, + string? save_to_path ) { Object ( - name: _(UNTITLED_NAME), parent: parent, rows: dimensions.height, cols: dimensions.width, @@ -42,7 +43,8 @@ public class Gnonograms.Filewriter : Object { difficulty: difficulty, save_dir_path: save_dir_path, game_path: game_path, - game_name: game_name + game_name: game_name, + save_to_path: save_to_path ); } @@ -52,12 +54,13 @@ public class Gnonograms.Filewriter : Object { /*** Writes minimum information required for valid game file ***/ public async void write_game_file (bool is_readonly) throws Error { + warning ("write game name %s", game_name); if (game_name == null) { game_name = _(UNTITLED_NAME); } - if (game_path == null || game_path.length <= 4) { - var game_file = yield Utils.get_open_save_file ( + if (save_to_path == null || save_to_path.length <= 4) { + var save_to_file = yield Utils.get_open_save_file ( parent, _("Name and save this puzzle"), true, @@ -65,43 +68,43 @@ public class Gnonograms.Filewriter : Object { game_name ); - if (game_file != null) { - game_path = game_file.get_path (); + if (save_to_file != null) { + save_to_path = save_to_file.get_path (); } } - if (game_path != null && - (game_path.length < 4 || - game_path[-4 : game_path.length] != Gnonograms.GAMEFILEEXTENSION)) { + if (save_to_path != null && + (save_to_path.length < 4 || + save_to_path[-4 : save_to_path.length] != Gnonograms.GAMEFILEEXTENSION)) { - game_path = game_path + Gnonograms.GAMEFILEEXTENSION; + save_to_path = save_to_path + Gnonograms.GAMEFILEEXTENSION; } - if (game_path == null) { - throw new IOError.CANCELLED ("No path selected"); + if (save_to_path == null) { + throw new IOError.CANCELLED ("No save path selected"); } - var file = File.new_for_commandline_arg (game_path); + var file = File.new_for_commandline_arg (save_to_path); if (file.query_exists () && !Utils.show_confirm_dialog ( - _("Overwrite %s").printf (game_path), + _("Overwrite %s").printf (save_to_path), _("This action will destroy contents of that file")) ) { throw new IOError.CANCELLED ("File exists"); } /* @game_path is local path, not a uri */ - stream = FileStream.open (game_path, "w"); + stream = FileStream.open (save_to_path, "w"); if (stream == null) { - throw new IOError.FAILED ("Could not open filestream to %s".printf (game_path)); + throw new IOError.FAILED ("Could not open filestream to %s".printf (save_to_path)); } - if (name == null || name.length == 0) { - throw new IOError.NOT_INITIALIZED ("No name to save"); - } + // if (name == null || name.length == 0) { + // throw new IOError.NOT_INITIALIZED ("No name to save"); + // } stream.printf ("[Description]\n"); - stream.printf ("%s\n", name); + stream.printf ("%s\n", game_name); stream.printf ("%s\n", author != "" ? author : "Gnonograms Generator"); stream.printf ("%s\n", date.to_string ()); stream.printf ("%u\n", difficulty); @@ -162,10 +165,10 @@ warning ("Writing working grid"); stream.printf (working.to_string ()); stream.printf ("[State]\n"); stream.printf (state.to_string () + "\n"); - if (game_name != _(UNTITLED_NAME)) { - stream.printf ("[Original path]\n"); - stream.printf (game_path.to_string () + "\n"); - } + // if (game_name != _(UNTITLED_NAME)) { + stream.printf ("[Original path]\n"); + stream.printf (game_path.to_string () + "\n"); + // } if (history != null) { stream.printf ("[History]\n"); From dba1dbfec7736edc4e4042c048acf953b0ef42dc Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 12 Dec 2024 11:02:58 +0000 Subject: [PATCH 133/142] Discontinue Lock key, use SaveFlags --- src/Controller.vala | 57 ++++++++++++++++++------------------ src/misc/utils.vala | 7 ++--- src/services/Filereader.vala | 32 ++++++++++---------- src/services/Filewriter.vala | 36 ++++++++++++----------- 4 files changed, 65 insertions(+), 67 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index c26db94..04b6393 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -8,6 +8,13 @@ public const string UNTITLED_NAME = N_("Untitled"); +[Flags] +public enum SaveFlags { + NONE, + SAVE_STATE, + CONFIRM_OVERWRITE +} + public class Gnonograms.Controller : GLib.Object { public static Controller get_default () { @@ -32,9 +39,9 @@ public class Gnonograms.Controller : GLib.Object { public uint columns { get; set; } // Stored with game file. Can be set be App Popover public string game_name { get; set; default = _(UNTITLED_NAME); } // Can be set be App Popover public string current_game_path { get; set; default = ""; } // synced with settings - /* Any game that was not saved by this app is regarded as read only - any alterations - * must be "Saved As" - which by default is writable. */ - public bool is_readonly { get; private set; default = false;} // Stored with game file. + // /* Any game that was not saved by this app is regarded as read only - any alterations + // * must be "Saved As" - which by default is writable. */ + // public bool is_readonly { get; private set; default = false;} // Stored with game file. public bool can_go_back { get { @@ -209,7 +216,7 @@ public class Gnonograms.Controller : GLib.Object { model.clear (); view.update_clues_from_solution (); clear_history (); - is_readonly = false; + // is_readonly = false; } private void new_game () { @@ -262,17 +269,16 @@ public class Gnonograms.Controller : GLib.Object { warning ("save game state"); string? saved_file_path = null; if (temporary_game_path != null) { - try { - var current_game_file = File.new_for_path (temporary_game_path); - current_game_file.@delete (); - } catch (Error e) { - /* Error normally thrown on first run */ - warning ("Error deleting temporary game file %s - %s", temporary_game_path, e.message); - } finally { - warning ("writing unsaved game to %s", temporary_game_path); - /* Save solution and current state */ - saved_file_path = yield write_game (temporary_game_path, true); - } + // try { + // var current_game_file = File.new_for_path (temporary_game_path); + // current_game_file.@delete (); // Unnecessary? + // } catch (Error e) { + // /* Error normally thrown on first run */ + // warning ("Error deleting temporary game file %s - %s", temporary_game_path, e.message); + // } finally { + // /* Save solution and current state */ + saved_file_path = yield write_game (temporary_game_path, SaveFlags.SAVE_STATE); + // } } return saved_file_path != null; @@ -285,7 +291,7 @@ public class Gnonograms.Controller : GLib.Object { yield save_game_as (); } else { warning ("write game - no state"); - var path = yield write_game (current_game_path, false); + var path = yield write_game (current_game_path, SaveFlags.NONE); if (path != null && path != "") { current_game_path = path; notify_saved (path); @@ -297,11 +303,11 @@ public class Gnonograms.Controller : GLib.Object { public async void save_game_as () { warning ("Controller: save game as"); /* Filewriter will request save location, no solution saved as default */ - var path = yield write_game (null, false); + var path = yield write_game (null, SaveFlags.CONFIRM_OVERWRITE); if (path != null) { current_game_path = path; notify_saved (path); - is_readonly = false; + // is_readonly = false; } } @@ -315,12 +321,7 @@ public class Gnonograms.Controller : GLib.Object { } } - private async string? write_game ( - string? save_to_path, - bool save_state = false - ) requires (saved_games_folder != null) { - - warning ("write game to %s, save state %s, save dir %s", save_to_path, save_state.to_string (), saved_games_folder); + private async string? write_game (string? save_to_path, SaveFlags flags) requires (saved_games_folder != null) { var file_writer = new Filewriter ( window, dimensions, @@ -339,16 +340,14 @@ public class Gnonograms.Controller : GLib.Object { var gs = game_state; game_state = LOAD_SAVE; try { - if (save_state) { + if (SAVE_STATE in flags) { yield file_writer.write_position_file ( model.copy_working_data (), gs, history ); } else { - yield file_writer.write_game_file ( - is_readonly - ); + yield file_writer.write_game_file (flags); } } catch (Error e) { if (!(e is IOError.CANCELLED)) { @@ -435,7 +434,7 @@ warning ("has state %s", reader.state.to_string ()); private async bool load_common (Filereader reader) { view.game_grade = reader.difficulty; - is_readonly = reader.is_readonly; + // is_readonly = reader.is_readonly; if (reader.has_dimensions) { if (reader.rows > MAXSIZE || reader.cols > MAXSIZE) { diff --git a/src/misc/utils.vala b/src/misc/utils.vala index 6be1be4..543bb9c 100644 --- a/src/misc/utils.vala +++ b/src/misc/utils.vala @@ -323,16 +323,13 @@ namespace Gnonograms.Utils { dialog.set_modal (true); File? result = null; + warning ("show dialog"); if (save) { result = yield (dialog.save (parent, null)); } else { result = yield (dialog.open (parent, null)); } - - - - - + warning ("done"); return result; } diff --git a/src/services/Filereader.vala b/src/services/Filereader.vala index b53e521..67d9efb 100644 --- a/src/services/Filereader.vala +++ b/src/services/Filereader.vala @@ -31,7 +31,7 @@ public class Gnonograms.Filereader : Object { public bool has_solution { get; private set; default = false;} public bool has_working { get; private set; default = false;} public bool has_state { get; private set; default = false;} - public bool is_readonly { get; private set; default = true;} + // public bool is_readonly { get; private set; default = true;} public bool valid { get { @@ -174,9 +174,9 @@ public class Gnonograms.Filereader : Object { in_error = !get_game_description (body); break; - case "LOC": - in_error = !get_readonly (body); - break; + // case "LOC": + // in_error = !get_readonly (body); + // break; case "ORI": in_error = !get_original_game_path (body); @@ -328,21 +328,21 @@ public class Gnonograms.Filereader : Object { return true; } - private bool get_readonly (string? body) { - if (body == null) { - return true; /* Not mandatory */ - } + // private bool get_readonly (string? body) { + // if (body == null) { + // return true; /* Not mandatory */ + // } - string[] s = Utils.remove_blank_lines (body.split ("\n")); - bool result = true; - if (s.length >= 1) { - bool.try_parse (s[0].down (), out result); - } + // string[] s = Utils.remove_blank_lines (body.split ("\n")); + // bool result = true; + // if (s.length >= 1) { + // bool.try_parse (s[0].down (), out result); + // } - is_readonly = result; + // // is_readonly = result; - return true; - } + // return true; + // } private bool get_original_game_path (string? body) { string result = ""; diff --git a/src/services/Filewriter.vala b/src/services/Filewriter.vala index 6408603..49e1744 100644 --- a/src/services/Filewriter.vala +++ b/src/services/Filewriter.vala @@ -53,7 +53,7 @@ public class Gnonograms.Filewriter : Object { } /*** Writes minimum information required for valid game file ***/ - public async void write_game_file (bool is_readonly) throws Error { + public async void write_game_file (SaveFlags flags) throws Error { warning ("write game name %s", game_name); if (game_name == null) { game_name = _(UNTITLED_NAME); @@ -71,7 +71,7 @@ public class Gnonograms.Filewriter : Object { if (save_to_file != null) { save_to_path = save_to_file.get_path (); } - } + } if (save_to_path != null && (save_to_path.length < 4 || @@ -85,12 +85,18 @@ public class Gnonograms.Filewriter : Object { } var file = File.new_for_commandline_arg (save_to_path); - if (file.query_exists () && - !Utils.show_confirm_dialog ( + if (CONFIRM_OVERWRITE in flags && + file.query_exists ()) { +warning ("confirming overwrite"); + var overwrite = Utils.show_confirm_dialog ( _("Overwrite %s").printf (save_to_path), - _("This action will destroy contents of that file")) - ) { - throw new IOError.CANCELLED ("File exists"); + _("This action will destroy contents of that file"), + parent + ); + + if (!overwrite) { + throw new IOError.CANCELLED ("File exists"); + } } /* @game_path is local path, not a uri */ @@ -147,28 +153,24 @@ public class Gnonograms.Filewriter : Object { stream.printf ("%s", solution.to_string ()); } - stream.printf ("[Locked]\n"); - stream.printf (is_readonly.to_string () + "\n"); + // stream.printf ("[Locked]\n"); + // stream.printf (is_readonly.to_string () + "\n"); } /*** Writes complete information to reload game state ***/ public async void write_position_file ( - My2DCellArray working, - GameState state, - History history + My2DCellArray working, + GameState state, + History history ) throws Error { -warning ("writing game file"); - yield write_game_file ( false ); + yield write_game_file (SaveFlags.NONE); -warning ("Writing working grid"); stream.printf ("[Working grid]\n"); stream.printf (working.to_string ()); stream.printf ("[State]\n"); stream.printf (state.to_string () + "\n"); - // if (game_name != _(UNTITLED_NAME)) { stream.printf ("[Original path]\n"); stream.printf (game_path.to_string () + "\n"); - // } if (history != null) { stream.printf ("[History]\n"); From b88d841e967b79540013ecd37868661514c2cbc5 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 12 Dec 2024 11:15:30 +0000 Subject: [PATCH 134/142] Cleanup --- src/Controller.vala | 48 +++++------------------------ src/HeaderBar/HeaderBarFactory.vala | 15 ++------- src/HeaderBar/HeaderButton.vala | 2 +- src/Model.vala | 6 ++-- src/misc/utils.vala | 10 ++---- src/objects/Move.vala | 24 ++------------- src/services/Filereader.vala | 21 ------------- src/services/Filewriter.vala | 10 ------ src/widgets/Cellgrid.vala | 26 ++++------------ 9 files changed, 23 insertions(+), 139 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index 04b6393..e8eaadc 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -16,7 +16,6 @@ public enum SaveFlags { } public class Gnonograms.Controller : GLib.Object { - public static Controller get_default () { if (instance == null) { instance = new Controller (); @@ -28,10 +27,6 @@ public class Gnonograms.Controller : GLib.Object { private static Controller? instance = null; - public signal void quit_app (); - public signal void dimensions_changed (uint rows, uint cols); - // public signal void changed_dimensions (uint rows, uint cols); - public Gtk.Window window { get { return (Gtk.Window)view;}} public GameState game_state { get; set; } // Stored with game file public Difficulty generator_grade { get; set; } // synced with settings @@ -39,9 +34,6 @@ public class Gnonograms.Controller : GLib.Object { public uint columns { get; set; } // Stored with game file. Can be set be App Popover public string game_name { get; set; default = _(UNTITLED_NAME); } // Can be set be App Popover public string current_game_path { get; set; default = ""; } // synced with settings - // /* Any game that was not saved by this app is regarded as read only - any alterations - // * must be "Saved As" - which by default is writable. */ - // public bool is_readonly { get; private set; default = false;} // Stored with game file. public bool can_go_back { get { @@ -53,7 +45,7 @@ public class Gnonograms.Controller : GLib.Object { return history.can_go_forward; } } - private View view; + private View view; private Model model; private Solver? solver; private SimpleRandomGameGenerator? generator; @@ -67,6 +59,9 @@ public class Gnonograms.Controller : GLib.Object { } } + public signal void quit_app (); + public signal void dimensions_changed (uint rows, uint cols); + private Controller () {} construct { history = new History (); @@ -101,8 +96,6 @@ public class Gnonograms.Controller : GLib.Object { saved_state.bind ("mode", this, "game-state", SettingsBindFlags.DEFAULT); saved_state.bind ("current-game-path", this, "current-game-path", SettingsBindFlags.DEFAULT); settings.bind ("grade", this, "generator-grade", SettingsBindFlags.DEFAULT); - // settings.bind ("rows", this, "rows", SettingsBindFlags.DEFAULT); - // settings.bind ("columns", this, "columns", SettingsBindFlags.DEFAULT); restore_game.begin ((obj, res) => { if (!restore_game.end (res)) { @@ -216,7 +209,6 @@ public class Gnonograms.Controller : GLib.Object { model.clear (); view.update_clues_from_solution (); clear_history (); - // is_readonly = false; } private void new_game () { @@ -258,7 +250,7 @@ public class Gnonograms.Controller : GLib.Object { } view.end_working (); - game_state = new_game_state; // app.game_state_changed (new_game_state); + game_state = new_game_state; generator = null; }); @@ -266,19 +258,9 @@ public class Gnonograms.Controller : GLib.Object { // Always saved to temp file, not original private async bool save_game_state () { - warning ("save game state"); string? saved_file_path = null; if (temporary_game_path != null) { - // try { - // var current_game_file = File.new_for_path (temporary_game_path); - // current_game_file.@delete (); // Unnecessary? - // } catch (Error e) { - // /* Error normally thrown on first run */ - // warning ("Error deleting temporary game file %s - %s", temporary_game_path, e.message); - // } finally { - // /* Save solution and current state */ - saved_file_path = yield write_game (temporary_game_path, SaveFlags.SAVE_STATE); - // } + saved_file_path = yield write_game (temporary_game_path, SaveFlags.SAVE_STATE); } return saved_file_path != null; @@ -286,11 +268,9 @@ public class Gnonograms.Controller : GLib.Object { // Called by save action public async void save_game () { - warning ("Controller: save game"); if (current_game_path == "") { yield save_game_as (); } else { - warning ("write game - no state"); var path = yield write_game (current_game_path, SaveFlags.NONE); if (path != null && path != "") { current_game_path = path; @@ -307,14 +287,12 @@ public class Gnonograms.Controller : GLib.Object { if (path != null) { current_game_path = path; notify_saved (path); - // is_readonly = false; } } private async bool restore_game () { if (temporary_game_path != null) { var current_game_file = File.new_for_path (temporary_game_path); - warning ("restore game %s", current_game_file.get_path ()); return yield load_game_async (current_game_file); } else { return false; @@ -386,7 +364,6 @@ public class Gnonograms.Controller : GLib.Object { game ); } catch (GLib.Error e) { - warning ("error on reading file %s", e.message); if (!(e is IOError.CANCELLED)) { var basename = game != null ? game.get_basename () : _("game"); var game_path = ""; @@ -406,16 +383,13 @@ public class Gnonograms.Controller : GLib.Object { game_state = SOLVING; // Default to solving to hide solution return false; } -warning ("reader read OK"); + if (reader.valid && (yield load_common (reader))) { -warning ("load common OK"); if (reader.has_working) { -warning ("has working"); model.set_working_data_from_string_array (reader.working[0 : dimensions.height]); } if (reader.has_state) { -warning ("has state %s", reader.state.to_string ()); game_state = reader.state; history.from_string (reader.moves); if (history.can_go_back) { @@ -434,7 +408,6 @@ warning ("has state %s", reader.state.to_string ()); private async bool load_common (Filereader reader) { view.game_grade = reader.difficulty; - // is_readonly = reader.is_readonly; if (reader.has_dimensions) { if (reader.rows > MAXSIZE || reader.cols > MAXSIZE) { @@ -478,7 +451,6 @@ warning ("has state %s", reader.state.to_string ()); model.blank_solution (); // Do not reveal solution on load if (reader.has_solution) { - warning ("has solution"); model.set_solution_data_from_string_array (reader.solution[0 : dimensions.height]); view.update_clues_from_solution (); /* Ensure completeness correctly set */ } @@ -489,8 +461,6 @@ warning ("has state %s", reader.state.to_string ()); yield; - -warning ("current game path now %s", current_game_path); return true; } @@ -501,7 +471,6 @@ warning ("current game path now %s", current_game_path); } var errors = model.count_errors (); - while (model.count_errors () > 0 && previous_move ()) { continue; } @@ -578,9 +547,7 @@ warning ("current game path now %s", current_game_path); } public bool on_delete_request () { - warning ("on delete request"); prepare_quit (); // Async - warning ("on view deleted returning true"); return true; } @@ -589,7 +556,6 @@ warning ("current game path now %s", current_game_path); } public void open_game () { - warning ("Controller: open game (null)"); load_game_async.begin (null); /* Filereader will request load location */ } diff --git a/src/HeaderBar/HeaderBarFactory.vala b/src/HeaderBar/HeaderBarFactory.vala index 6c44ec7..e549149 100644 --- a/src/HeaderBar/HeaderBarFactory.vala +++ b/src/HeaderBar/HeaderBarFactory.vala @@ -117,7 +117,6 @@ public class Gnonograms.HeaderBarFactory : Object { }; progress_stack.add_named (progress_indicator, "Progress"); progress_stack.add_named (title_label, "Title"); - // progress_stack.add_named (new Gtk.Label (""), "None"); progress_stack.set_visible_child_name ("Title"); header_bar = new Gtk.HeaderBar () { @@ -180,13 +179,7 @@ public class Gnonograms.HeaderBarFactory : Object { public void update_title (string name, string path, Difficulty grade) { title_label.label = name; title_label.tooltip_text = path; - // if (grade != UNDEFINED) { - // warning ("show TITLE"); - progress_stack.set_visible_child_name ("Title"); - // } else { - // warning ("show NONE"); - // progress_stack.set_visible_child_name ("None"); - // } + progress_stack.set_visible_child_name ("Title"); } public void show_working (string text) { @@ -194,11 +187,7 @@ public class Gnonograms.HeaderBarFactory : Object { } public void hide_progress (Difficulty game_grade) { - // if (game_grade != Difficulty.UNDEFINED) { - progress_stack.set_visible_child_name ("Title"); - // } else { - // progress_stack.set_visible_child_name ("None"); - // } + progress_stack.set_visible_child_name ("Title"); } public void show_progress (Cancellable? cancellable) { diff --git a/src/HeaderBar/HeaderButton.vala b/src/HeaderBar/HeaderButton.vala index c12c6b8..267d6f6 100644 --- a/src/HeaderBar/HeaderButton.vala +++ b/src/HeaderBar/HeaderButton.vala @@ -9,7 +9,7 @@ Object ( action_name: action_name, tooltip_markup: Granite.markup_accel_tooltip ( - ((App)(Application.get_default ())).get_accels_for_action (action_name), + ((App)(Application.get_default ())).get_accels_for_action (action_name), text ), valign: Gtk.Align.CENTER diff --git a/src/Model.vala b/src/Model.vala index 1e4ac0e..252b1c9 100644 --- a/src/Model.vala +++ b/src/Model.vala @@ -9,12 +9,12 @@ public class Gnonograms.Model : GLib.Object { if (instance == null) { instance = new Model (); } - + return instance; } - + private static Model? instance; - + public signal void changed (); public My2DCellArray display_data { diff --git a/src/misc/utils.vala b/src/misc/utils.vala index 543bb9c..67e8fc7 100644 --- a/src/misc/utils.vala +++ b/src/misc/utils.vala @@ -198,8 +198,8 @@ namespace Gnonograms.Utils { } else if (count_state == CellState.UNKNOWN) { sb.append ("?" + BLOCKSEPARATOR); blocks++; - } - + } + if (blocks == 0) { sb.append ("0"); } else { @@ -332,10 +332,4 @@ namespace Gnonograms.Utils { warning ("done"); return result; } - - // public Gdk.Rectangle get_monitor_area (Gdk.Surface surface) { - // var display = Gdk.Display.get_default (); - // var monitor = display.get_monitor_at_surface (surface); - // return monitor.get_geometry (); - // } } diff --git a/src/objects/Move.vala b/src/objects/Move.vala index 46a84a7..91ab808 100644 --- a/src/objects/Move.vala +++ b/src/objects/Move.vala @@ -5,8 +5,6 @@ * Authored by: Jeremy Wootten */ public class Gnonograms.Move { - // public static Move null_move = new Move (NULL_CELL, CellState.UNDEFINED); - public Cell cell; public CellState previous_state; @@ -19,7 +17,7 @@ public class Gnonograms.Move { previous_state = _previous_state; } - + public Move (uint _row, uint _col, CellState _state, CellState _previous_state) { cell = Cell () { row =_row, @@ -38,7 +36,7 @@ public class Gnonograms.Move { previous_state < CellState.COMPLETED ); } - + public bool equal (Move? m) { return m != null && (m.cell.equal (cell) && m.previous_state == previous_state); } @@ -47,19 +45,11 @@ public class Gnonograms.Move { return new Move.from_cell (this.cell.clone (), this.previous_state); } - // public bool is_null () { - // return equal (Move.null_move); - // } - public string to_string () { return "%u,%u,%u,%u".printf (cell.row, cell.col, cell.state, previous_state); } public static Move? from_string (string s) throws ConvertError { - // if (s == null) { - // return Move.null_move; - // } - var parts = s.split (","); if (parts == null || parts.length != 4) { // return Move.null_move; @@ -70,16 +60,6 @@ public class Gnonograms.Move { var col = (uint)(int.parse (parts[1])); var state = (uint)(int.parse (parts[2])); var previous_state = (uint)(int.parse (parts[3])); - - // if (row > MAXSIZE || - // col > MAXSIZE || - // state > CellState.COMPLETED || - // previous_state > CellState.COMPLETED) { - - // throw new ConvertError.FAILED ("Invalid location or state"); - // } - - // Cell c = {row, col, state}; var mv = new Move (row, col, state, previous_state); if (mv.is_valid ()) { return mv; diff --git a/src/services/Filereader.vala b/src/services/Filereader.vala index 67d9efb..2215cdf 100644 --- a/src/services/Filereader.vala +++ b/src/services/Filereader.vala @@ -31,7 +31,6 @@ public class Gnonograms.Filereader : Object { public bool has_solution { get; private set; default = false;} public bool has_working { get; private set; default = false;} public bool has_state { get; private set; default = false;} - // public bool is_readonly { get; private set; default = true;} public bool valid { get { @@ -174,10 +173,6 @@ public class Gnonograms.Filereader : Object { in_error = !get_game_description (body); break; - // case "LOC": - // in_error = !get_readonly (body); - // break; - case "ORI": in_error = !get_original_game_path (body); break; @@ -328,22 +323,6 @@ public class Gnonograms.Filereader : Object { return true; } - // private bool get_readonly (string? body) { - // if (body == null) { - // return true; /* Not mandatory */ - // } - - // string[] s = Utils.remove_blank_lines (body.split ("\n")); - // bool result = true; - // if (s.length >= 1) { - // bool.try_parse (s[0].down (), out result); - // } - - // // is_readonly = result; - - // return true; - // } - private bool get_original_game_path (string? body) { string result = ""; if (body != null) { diff --git a/src/services/Filewriter.vala b/src/services/Filewriter.vala index 49e1744..e305007 100644 --- a/src/services/Filewriter.vala +++ b/src/services/Filewriter.vala @@ -11,7 +11,6 @@ public class Gnonograms.Filewriter : Object { public My2DCellArray? solution { get; set; default = null;} public uint rows { get; construct; } public uint cols { get; construct; } - // public string name { get; set; } public string[] row_clues { get; construct; } public string[] col_clues { get; construct; } public string? game_path { get; set construct; } @@ -54,7 +53,6 @@ public class Gnonograms.Filewriter : Object { /*** Writes minimum information required for valid game file ***/ public async void write_game_file (SaveFlags flags) throws Error { - warning ("write game name %s", game_name); if (game_name == null) { game_name = _(UNTITLED_NAME); } @@ -87,7 +85,6 @@ public class Gnonograms.Filewriter : Object { var file = File.new_for_commandline_arg (save_to_path); if (CONFIRM_OVERWRITE in flags && file.query_exists ()) { -warning ("confirming overwrite"); var overwrite = Utils.show_confirm_dialog ( _("Overwrite %s").printf (save_to_path), _("This action will destroy contents of that file"), @@ -105,10 +102,6 @@ warning ("confirming overwrite"); throw new IOError.FAILED ("Could not open filestream to %s".printf (save_to_path)); } - // if (name == null || name.length == 0) { - // throw new IOError.NOT_INITIALIZED ("No name to save"); - // } - stream.printf ("[Description]\n"); stream.printf ("%s\n", game_name); stream.printf ("%s\n", author != "" ? author : "Gnonograms Generator"); @@ -152,9 +145,6 @@ warning ("confirming overwrite"); stream.printf ("[Solution grid]\n"); stream.printf ("%s", solution.to_string ()); } - - // stream.printf ("[Locked]\n"); - // stream.printf (is_readonly.to_string () + "\n"); } /*** Writes complete information to reload game state ***/ diff --git a/src/widgets/Cellgrid.vala b/src/widgets/Cellgrid.vala index b2cf621..0adf941 100644 --- a/src/widgets/Cellgrid.vala +++ b/src/widgets/Cellgrid.vala @@ -65,8 +65,6 @@ public struct Gnonograms.Cell { public class Gnonograms.CellGrid : Gtk.DrawingArea { public signal void leave (); - // public unowned View view { get; construct; } - // public Model model { get; construct; } public Cell? current_cell { get; set; } public Cell? previous_cell { get; set; } public bool frozen { get; set; } @@ -123,18 +121,10 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { return model.display_data; } } - + private Controller controller = Controller.get_default (); private Model model = Model.get_default (); - // private App app = ((App)(Application.get_default ())); - - // public CellGrid (Model model) { - // Object ( - // model: model - // ); - // } - construct { hexpand = true; vexpand = true; @@ -156,8 +146,6 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { }); controller.notify["game-state"].connect (on_game_state_changed); - // app.bind_property ("game-state", this, "game-state"); - // app.game_state_changed.connect (on_game_state_changed); controller.dimensions_changed.connect (on_dimensions_changed); model.changed.connect (() => { @@ -194,12 +182,10 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { }); } - // private GameState gs; private void on_game_state_changed () { - // this.gs = gs; update_colors (controller.game_state); } - + private void update_colors (GameState gs) { unknown_color = colors[(int)gs, (int)CellState.UNKNOWN]; fill_color = colors[(int)gs, (int)CellState.FILLED]; @@ -243,12 +229,12 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { } private void draw_func ( - Gtk.DrawingArea drawing_area, - Cairo.Context cr, - int x, + Gtk.DrawingArea drawing_area, + Cairo.Context cr, + int x, int y ) { - + dirty = false; if (array != null) { /* Note, even tho' array holds CellStates, its iterator returns Cells */ From 60c5d1f921aa7309e844415f081dc3177c5dbb96 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 12 Dec 2024 11:51:13 +0000 Subject: [PATCH 135/142] Rename Factory->Manager --- meson.build | 2 +- ...rBarFactory.vala => HeaderBarManager.vala} | 4 ++-- src/View.vala | 20 +++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) rename src/HeaderBar/{HeaderBarFactory.vala => HeaderBarManager.vala} (98%) diff --git a/meson.build b/meson.build index 1db4c96..ebf9876 100644 --- a/meson.build +++ b/meson.build @@ -43,7 +43,7 @@ executable ( 'src/View.vala', 'src/Model.vala', - 'src/HeaderBar/HeaderBarFactory.vala', + 'src/HeaderBar/HeaderBarManager.vala', 'src/HeaderBar/HeaderButton.vala', 'src/HeaderBar/PopoverButton.vala', 'src/HeaderBar/ProgressIndicator.vala', diff --git a/src/HeaderBar/HeaderBarFactory.vala b/src/HeaderBar/HeaderBarManager.vala similarity index 98% rename from src/HeaderBar/HeaderBarFactory.vala rename to src/HeaderBar/HeaderBarManager.vala index e549149..bd09a59 100644 --- a/src/HeaderBar/HeaderBarFactory.vala +++ b/src/HeaderBar/HeaderBarManager.vala @@ -5,7 +5,7 @@ * Authored by: Jeremy Wootten */ -public class Gnonograms.HeaderBarFactory : Object { +public class Gnonograms.HeaderBarManager : Object { public View view { get; construct; } @@ -23,7 +23,7 @@ public class Gnonograms.HeaderBarFactory : Object { private Gtk.Button auto_solve_button; private Gtk.Button restart_button; - public HeaderBarFactory (Gnonograms.View view) { + public HeaderBarManager (Gnonograms.View view) { Object ( view: view ); diff --git a/src/View.vala b/src/View.vala index e4a0c4b..1c2e865 100644 --- a/src/View.vala +++ b/src/View.vala @@ -68,7 +68,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private ClueBox column_clue_box; private CellGrid cell_grid; private Gtk.MenuButton menu_button; - private HeaderBarFactory headerbar_factory; + private HeaderBarManager headerbar_manager; private Gtk.Grid main_grid; private Adw.ToastOverlay toast_overlay; private uint drawing_with_key = 0; @@ -143,9 +143,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { app.set_accels_for_action (ACTION_PREFIX + action, accels_array); } - headerbar_factory = new HeaderBarFactory (this); + headerbar_manager = new HeaderBarManager (this); - set_titlebar (headerbar_factory.get_headerbar ()); + set_titlebar (headerbar_manager.get_headerbar ()); row_clue_box = new ClueBox (false); column_clue_box = new ClueBox (true); @@ -265,7 +265,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { var gs = controller.game_state; update_all_labels_completeness (); restart_destructive = !model.is_blank (gs); - headerbar_factory.on_game_state_changed (gs); + headerbar_manager.on_game_state_changed (gs); } @@ -312,7 +312,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public void show_working (Cancellable cancellable, string text = "") { cell_grid.frozen = true; // Do not show model updates schedule_show_progress (cancellable); - headerbar_factory.show_working (text); + headerbar_manager.show_working (text); } public void end_working () { @@ -322,18 +322,18 @@ public class Gnonograms.View : Gtk.ApplicationWindow { progress_timeout_id = 0; } - headerbar_factory.hide_progress (game_grade); + headerbar_manager.hide_progress (game_grade); update_all_labels_completeness (); } public void update_title () { warning ("View: update title %s", controller.game_name); - headerbar_factory.update_title (controller.game_name, controller.current_game_path, game_grade); + headerbar_manager.update_title (controller.game_name, controller.current_game_path, game_grade); } public void on_can_go_changed (bool forward, bool back) { - headerbar_factory.on_can_go_changed (forward, back); + headerbar_manager.on_can_go_changed (forward, back); } private void highlight_labels (Cell? c, bool is_highlight) { @@ -417,7 +417,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { Priority.HIGH_IDLE, PROGRESS_DELAY_MSEC, () => { - headerbar_factory.show_progress (cancellable); + headerbar_manager.show_progress (cancellable); progress_timeout_id = 0; return false; } @@ -450,7 +450,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { } private void action_preferences () { - headerbar_factory.popdown_menus (); + headerbar_manager.popdown_menus (); var dialog = new PreferencesDialog () { transient_for = this, title = _("Preferences") From 6b74609fcce45adb5e9ca0c5def25efdbc925191 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 12 Dec 2024 13:02:22 +0000 Subject: [PATCH 136/142] Show grade in header; fix load grade and author --- src/Controller.vala | 20 ++++++++++++-------- src/HeaderBar/HeaderBarManager.vala | 25 +++++++++++++++++++------ src/View.vala | 3 --- src/services/Filereader.vala | 13 +++++++++++-- src/services/Filewriter.vala | 21 ++++++++------------- 5 files changed, 50 insertions(+), 32 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index e8eaadc..e23fa89 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -33,6 +33,7 @@ public class Gnonograms.Controller : GLib.Object { public uint rows { get; set; } // Stored with game file. Can be set be App Popover public uint columns { get; set; } // Stored with game file. Can be set be App Popover public string game_name { get; set; default = _(UNTITLED_NAME); } // Can be set be App Popover + public string author { get; set; default = _("Unknown"); } // Can be set be App Popover public string current_game_path { get; set; default = ""; } // synced with settings public bool can_go_back { @@ -215,11 +216,17 @@ public class Gnonograms.Controller : GLib.Object { clear (); game_state = GameState.SETTING; game_name = _(UNTITLED_NAME); + current_game_path = ""; } private void on_new_random_request () { clear (); solver.cancel (); + author = APP_NAME; + current_game_path = ""; + game_name = _("Random pattern"); + view.game_grade = Difficulty.UNDEFINED; + game_state = GameState.GENERATING; var cancellable = new Cancellable (); solver.cancellable = cancellable; @@ -227,9 +234,6 @@ public class Gnonograms.Controller : GLib.Object { grade = generator_grade }; - game_name = _("Random pattern"); - view.game_grade = Difficulty.UNDEFINED; - game_state = GameState.GENERATING; view.show_working (cancellable, (_("Generating"))); generator.generate.begin ((obj, res) => { var success = generator.generate.end (res); @@ -305,14 +309,14 @@ public class Gnonograms.Controller : GLib.Object { dimensions, view.get_clues (false), view.get_clues (true), - view.game_grade, saved_games_folder, current_game_path, - game_name, save_to_path - ) { - solution = !model.solution_is_blank () ? model.copy_solution_data () : null + solution = !model.solution_is_blank () ? model.copy_solution_data () : null, + game_name = this.game_name, + author = this.author, + difficulty = view.game_grade }; var gs = game_state; @@ -408,7 +412,7 @@ public class Gnonograms.Controller : GLib.Object { private async bool load_common (Filereader reader) { view.game_grade = reader.difficulty; - +warning ("load common: difficulty %s", reader.difficulty.to_string ()); if (reader.has_dimensions) { if (reader.rows > MAXSIZE || reader.cols > MAXSIZE) { reader.err_msg = (_("Dimensions too large")); diff --git a/src/HeaderBar/HeaderBarManager.vala b/src/HeaderBar/HeaderBarManager.vala index bd09a59..c403a9f 100644 --- a/src/HeaderBar/HeaderBarManager.vala +++ b/src/HeaderBar/HeaderBarManager.vala @@ -10,7 +10,8 @@ public class Gnonograms.HeaderBarManager : Object { public View view { get; construct; } private Gtk.HeaderBar header_bar; - private Gtk.Label title_label; + private Gtk.EditableLabel title_label; + private Gtk.Label grade_label; private Gtk.Stack progress_stack; private ProgressIndicator progress_indicator; private Gtk.Button generate_button; @@ -106,17 +107,27 @@ public class Gnonograms.HeaderBarManager : Object { progress_indicator = new ProgressIndicator (); - title_label = new Gtk.Label ("Gnonograms") { - use_markup = true, + title_label = new Gtk.EditableLabel ("Gnonograms") { + // use_markup = true, xalign = 0.5f }; - title_label.add_css_class (Granite.STYLE_CLASS_H3_LABEL); + title_label.add_css_class (Granite.STYLE_CLASS_TITLE_LABEL); + + grade_label = new Gtk.Label ("") { + xalign = 0.5f + }; + grade_label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL); + grade_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); + + var label_box = new Gtk.Box (VERTICAL, 0); + label_box.append (title_label); + label_box.append (grade_label); progress_stack = new Gtk.Stack () { halign = Gtk.Align.CENTER, }; progress_stack.add_named (progress_indicator, "Progress"); - progress_stack.add_named (title_label, "Title"); + progress_stack.add_named (label_box, "Title"); progress_stack.set_visible_child_name ("Title"); header_bar = new Gtk.HeaderBar () { @@ -177,7 +188,9 @@ public class Gnonograms.HeaderBarManager : Object { } public void update_title (string name, string path, Difficulty grade) { - title_label.label = name; +warning ("update title - grade %s", grade.to_string ()); + title_label.text = name; + grade_label.label = grade.to_string (); title_label.tooltip_text = path; progress_stack.set_visible_child_name ("Title"); } diff --git a/src/View.vala b/src/View.vala index 1c2e865..add209c 100644 --- a/src/View.vala +++ b/src/View.vala @@ -58,7 +58,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public Cell? previous_cell { get; set; } public Difficulty generator_grade { get; set; } public Difficulty game_grade { get; set; } - // public string game_name { get { return controller.game_name; } } public bool readonly { get; set; default = false;} public bool restart_destructive { get; set; default = false;} @@ -576,11 +575,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { controller.change_mode (SETTING); } private void action_solving_mode () { - // controller.game_state = GameState.SOLVING; controller.change_mode (SOLVING); } private void action_generating_mode () { - // controller.game_state = GameState.GENERATING; controller.change_mode (GENERATING); } diff --git a/src/services/Filereader.vala b/src/services/Filereader.vala index 2215cdf..15b8456 100644 --- a/src/services/Filereader.vala +++ b/src/services/Filereader.vala @@ -19,6 +19,7 @@ public class Gnonograms.Filereader : Object { public string[] working { get; private set; } public string name { get; private set; default = "";} + public string author { get; private set; default = "";} public string date { get; private set; default = "";} public Difficulty difficulty { get; private set; default = Difficulty.UNDEFINED;} public string license { get; private set; default = "";} @@ -308,16 +309,24 @@ public class Gnonograms.Filereader : Object { } if (s.length >= 2) { - date = s[1]; + author = s[1]; } if (s.length >= 3) { - var grade = s[2].strip (); + date = s[2]; + } + + if (s.length >= 4) { + warning ("reading difficulty"); + var grade = s[3].strip (); + warning ("read %s", grade); if (grade.length == 1 && grade[0].isdigit ()) { difficulty = (Difficulty)(int.parse (grade)); } else { difficulty = Difficulty.UNDEFINED; } + + warning ("got %s", difficulty.to_string ()); } return true; diff --git a/src/services/Filewriter.vala b/src/services/Filewriter.vala index e305007..a0c04ac 100644 --- a/src/services/Filewriter.vala +++ b/src/services/Filewriter.vala @@ -7,18 +7,21 @@ public class Gnonograms.Filewriter : Object { public DateTime date { get; construct; } public Gtk.Window? parent { get; construct; } - public Difficulty difficulty { get; set; default = Difficulty.UNDEFINED;} - public My2DCellArray? solution { get; set; default = null;} + public uint rows { get; construct; } public uint cols { get; construct; } public string[] row_clues { get; construct; } public string[] col_clues { get; construct; } public string? game_path { get; set construct; } public string? save_to_path { get; set construct; } - public string? game_name { get; set construct; } public string? save_dir_path { get; construct; } - public string author { get; set; default = "";} + + public My2DCellArray? solution { get; set; default = null;} + public string game_name { get; set; default = _(UNTITLED_NAME); } + public Difficulty difficulty { get; set; default = Difficulty.UNDEFINED;} + public string author { get; set; default = "Unknown";} public string license { get; set; default = "";} + private FileStream? stream; public Filewriter ( @@ -26,10 +29,8 @@ public class Gnonograms.Filewriter : Object { Dimensions dimensions, string[] row_clues, string[] col_clues, - Difficulty difficulty, string? save_dir_path, string? game_path, - string game_name, string? save_to_path ) { @@ -39,10 +40,8 @@ public class Gnonograms.Filewriter : Object { cols: dimensions.width, row_clues: row_clues, col_clues: col_clues, - difficulty: difficulty, save_dir_path: save_dir_path, game_path: game_path, - game_name: game_name, save_to_path: save_to_path ); } @@ -53,10 +52,6 @@ public class Gnonograms.Filewriter : Object { /*** Writes minimum information required for valid game file ***/ public async void write_game_file (SaveFlags flags) throws Error { - if (game_name == null) { - game_name = _(UNTITLED_NAME); - } - if (save_to_path == null || save_to_path.length <= 4) { var save_to_file = yield Utils.get_open_save_file ( parent, @@ -104,7 +99,7 @@ public class Gnonograms.Filewriter : Object { stream.printf ("[Description]\n"); stream.printf ("%s\n", game_name); - stream.printf ("%s\n", author != "" ? author : "Gnonograms Generator"); + stream.printf ("%s\n", author); stream.printf ("%s\n", date.to_string ()); stream.printf ("%u\n", difficulty); From 5bcee65f908d4cfcda9ab89dbaa5e6e929cda624 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 12 Dec 2024 13:11:39 +0000 Subject: [PATCH 137/142] Sync editable label text --- src/HeaderBar/HeaderBarManager.vala | 7 +++++-- src/View.vala | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/HeaderBar/HeaderBarManager.vala b/src/HeaderBar/HeaderBarManager.vala index c403a9f..54a9ddb 100644 --- a/src/HeaderBar/HeaderBarManager.vala +++ b/src/HeaderBar/HeaderBarManager.vala @@ -24,6 +24,8 @@ public class Gnonograms.HeaderBarManager : Object { private Gtk.Button auto_solve_button; private Gtk.Button restart_button; + private Controller controller = Controller.get_default (); + public HeaderBarManager (Gnonograms.View view) { Object ( view: view @@ -145,6 +147,8 @@ public class Gnonograms.HeaderBarManager : Object { header_bar.pack_end (mode_switch); header_bar.pack_end (auto_solve_button); + title_label.bind_property ("text", controller, "game-name", BIDIRECTIONAL); + view.bind_property ( "restart-destructive", restart_button, "restart-destructive", @@ -187,9 +191,8 @@ public class Gnonograms.HeaderBarManager : Object { redo_button.sensitive = forward; } - public void update_title (string name, string path, Difficulty grade) { + public void update_title (string path, Difficulty grade) { warning ("update title - grade %s", grade.to_string ()); - title_label.text = name; grade_label.label = grade.to_string (); title_label.tooltip_text = path; progress_stack.set_visible_child_name ("Title"); diff --git a/src/View.vala b/src/View.vala index add209c..31aba82 100644 --- a/src/View.vala +++ b/src/View.vala @@ -232,7 +232,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { ); controller.notify["game-state"].connect (on_game_state_changed); - controller.notify["game-name"].connect (update_title); controller.notify["current-game-path"].connect (update_title); notify["game-grade"].connect (update_title); @@ -328,7 +327,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public void update_title () { warning ("View: update title %s", controller.game_name); - headerbar_manager.update_title (controller.game_name, controller.current_game_path, game_grade); + headerbar_manager.update_title (controller.current_game_path, game_grade); } public void on_can_go_changed (bool forward, bool back) { From 38a9ed9b465014f5aa5946475a72c5e8d7a1f388 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 12 Dec 2024 13:20:35 +0000 Subject: [PATCH 138/142] Fix title tooltip --- src/HeaderBar/HeaderBarManager.vala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/HeaderBar/HeaderBarManager.vala b/src/HeaderBar/HeaderBarManager.vala index 54a9ddb..bd2844c 100644 --- a/src/HeaderBar/HeaderBarManager.vala +++ b/src/HeaderBar/HeaderBarManager.vala @@ -127,16 +127,19 @@ public class Gnonograms.HeaderBarManager : Object { progress_stack = new Gtk.Stack () { halign = Gtk.Align.CENTER, + hexpand = true }; progress_stack.add_named (progress_indicator, "Progress"); progress_stack.add_named (label_box, "Title"); progress_stack.set_visible_child_name ("Title"); + progress_stack.add_css_class ("title"); header_bar = new Gtk.HeaderBar () { show_title_buttons = true, title_widget = progress_stack }; - header_bar.add_css_class ("gnonograms-header"); + + header_bar.pack_start (generate_button); header_bar.pack_start (hint_button); header_bar.pack_start (restart_button); @@ -192,9 +195,8 @@ public class Gnonograms.HeaderBarManager : Object { } public void update_title (string path, Difficulty grade) { -warning ("update title - grade %s", grade.to_string ()); grade_label.label = grade.to_string (); - title_label.tooltip_text = path; + progress_stack.tooltip_text = path; progress_stack.set_visible_child_name ("Title"); } From d8b5b08f500e653120096e7e6e3a208b025a1462 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 12 Dec 2024 16:00:50 +0000 Subject: [PATCH 139/142] Move game-grade to Controller --- src/Controller.vala | 88 ++++++++++++++--------------- src/HeaderBar/HeaderBarManager.vala | 11 +++- src/View.vala | 21 +++---- src/misc/Enums.vala | 8 --- 4 files changed, 58 insertions(+), 70 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index e23fa89..f1a5992 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -28,13 +28,19 @@ public class Gnonograms.Controller : GLib.Object { private static Controller? instance = null; public Gtk.Window window { get { return (Gtk.Window)view;}} - public GameState game_state { get; set; } // Stored with game file - public Difficulty generator_grade { get; set; } // synced with settings - public uint rows { get; set; } // Stored with game file. Can be set be App Popover - public uint columns { get; set; } // Stored with game file. Can be set be App Popover + + // Settings + public string saved_path { get; private set; default = ""; } // Where saved (not temporary file) + public Difficulty generator_grade { get; set; } // Target difficulty of generator. Set in AppPopover + + // Game details + public GameState game_state { get; set; } // Whether solving or designing + public Difficulty game_grade { get; private set; default = UNDEFINED; } // Difficulty of the game, if known + public uint rows { get; set; } // Can be set be App Popover + public uint columns { get; set; } // Can be set be App Popover public string game_name { get; set; default = _(UNTITLED_NAME); } // Can be set be App Popover - public string author { get; set; default = _("Unknown"); } // Can be set be App Popover - public string current_game_path { get; set; default = ""; } // synced with settings + public string author { get; set; default = _("Unknown"); } //TODO Can be set be App Popover + public bool can_go_back { get { @@ -46,6 +52,7 @@ public class Gnonograms.Controller : GLib.Object { return history.can_go_forward; } } + private View view; private Model model; private Solver? solver; @@ -67,9 +74,6 @@ public class Gnonograms.Controller : GLib.Object { construct { history = new History (); - notify["rows"].connect (on_dimensions_changed); - notify["columns"].connect (on_dimensions_changed); - var data_home_folder_current = Path.build_path ( Path.DIR_SEPARATOR_S, Environment.get_user_config_dir (), @@ -87,7 +91,7 @@ public class Gnonograms.Controller : GLib.Object { saved_games_folder = Environment.get_user_special_dir (UserDirectory.DOCUMENTS); - current_game_path = ""; + saved_path = ""; temporary_game_path = Path.build_path ( Path.DIR_SEPARATOR_S, data_home_folder_current, @@ -97,15 +101,8 @@ public class Gnonograms.Controller : GLib.Object { saved_state.bind ("mode", this, "game-state", SettingsBindFlags.DEFAULT); saved_state.bind ("current-game-path", this, "current-game-path", SettingsBindFlags.DEFAULT); settings.bind ("grade", this, "generator-grade", SettingsBindFlags.DEFAULT); - - restore_game.begin ((obj, res) => { - if (!restore_game.end (res)) { - /* Error normally thrown if running without installing */ - warning ("Restoring game failed"); - restore_defaults (); - new_game (); - } - }); + notify["rows"].connect (on_dimensions_changed); + notify["columns"].connect (on_dimensions_changed); } protected void set_model_and_view () { @@ -130,26 +127,25 @@ public class Gnonograms.Controller : GLib.Object { saved_state.bind ("window-height", view, "default-height", SettingsBindFlags.SET); saved_state.bind ("window-width", view, "default-width", SettingsBindFlags.SET); - bind_property ( - "generator-grade", - view, "generator-grade", - BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL - ); - bind_property ( - "is-readonly", - view, "readonly", - BindingFlags.SYNC_CREATE - ); - history.can_go_changed.connect ((forward, back) => { view.on_can_go_changed (forward, back); }); + + + restore_game.begin ((obj, res) => { + if (!restore_game.end (res)) { + /* Error normally thrown if running without installing */ + warning ("Restoring game failed"); + restore_defaults (); + new_game (); + } + }); } private void restore_defaults () { rows = 10; columns = 15; - view.game_grade = Difficulty.UNDEFINED; + game_grade = Difficulty.UNDEFINED; game_state = GameState.SETTING; } @@ -216,16 +212,16 @@ public class Gnonograms.Controller : GLib.Object { clear (); game_state = GameState.SETTING; game_name = _(UNTITLED_NAME); - current_game_path = ""; + saved_path = ""; } private void on_new_random_request () { clear (); solver.cancel (); author = APP_NAME; - current_game_path = ""; + saved_path = ""; game_name = _("Random pattern"); - view.game_grade = Difficulty.UNDEFINED; + game_grade = Difficulty.UNDEFINED; game_state = GameState.GENERATING; var cancellable = new Cancellable (); @@ -242,7 +238,7 @@ public class Gnonograms.Controller : GLib.Object { model.set_solution_from_array (generator.get_solution ()); new_game_state = GameState.SOLVING; view.update_clues_from_solution (); - view.game_grade = generator.solution_grade; + game_grade = generator.solution_grade; } else { clear (); new_game_state = GameState.SETTING; @@ -272,12 +268,12 @@ public class Gnonograms.Controller : GLib.Object { // Called by save action public async void save_game () { - if (current_game_path == "") { + if (saved_path == "") { yield save_game_as (); } else { - var path = yield write_game (current_game_path, SaveFlags.NONE); + var path = yield write_game (saved_path, SaveFlags.NONE); if (path != null && path != "") { - current_game_path = path; + saved_path = path; notify_saved (path); } } @@ -289,7 +285,7 @@ public class Gnonograms.Controller : GLib.Object { /* Filewriter will request save location, no solution saved as default */ var path = yield write_game (null, SaveFlags.CONFIRM_OVERWRITE); if (path != null) { - current_game_path = path; + saved_path = path; notify_saved (path); } } @@ -310,13 +306,13 @@ public class Gnonograms.Controller : GLib.Object { view.get_clues (false), view.get_clues (true), saved_games_folder, - current_game_path, + saved_path, save_to_path ) { solution = !model.solution_is_blank () ? model.copy_solution_data () : null, game_name = this.game_name, author = this.author, - difficulty = view.game_grade + difficulty = game_grade }; var gs = game_state; @@ -411,8 +407,7 @@ public class Gnonograms.Controller : GLib.Object { } private async bool load_common (Filereader reader) { - view.game_grade = reader.difficulty; -warning ("load common: difficulty %s", reader.difficulty.to_string ()); + game_grade = reader.difficulty; if (reader.has_dimensions) { if (reader.rows > MAXSIZE || reader.cols > MAXSIZE) { reader.err_msg = (_("Dimensions too large")); @@ -444,10 +439,9 @@ warning ("load common: difficulty %s", reader.difficulty.to_string ()); if (reader.original_path != null && reader.original_path != "") { - warning ("has original path %s", reader.original_path); - current_game_path = reader.original_path; + saved_path = reader.original_path; } else { - current_game_path = reader.game_file.get_path (); + saved_path = reader.game_file.get_path (); } Idle.add (() => { // Need time for model to update dimensions through notify signal @@ -630,7 +624,7 @@ warning ("load common: difficulty %s", reader.difficulty.to_string ()); view.send_notification (msg); } - view.game_grade = diff; + game_grade = diff; view.end_working (); return state; } diff --git a/src/HeaderBar/HeaderBarManager.vala b/src/HeaderBar/HeaderBarManager.vala index bd2844c..7a3c40d 100644 --- a/src/HeaderBar/HeaderBarManager.vala +++ b/src/HeaderBar/HeaderBarManager.vala @@ -23,6 +23,11 @@ public class Gnonograms.HeaderBarManager : Object { private AppPopover app_popover; private Gtk.Button auto_solve_button; private Gtk.Button restart_button; + public Difficulty game_grade { + set { + grade_label.label = value.to_string (); + } + } private Controller controller = Controller.get_default (); @@ -150,7 +155,9 @@ public class Gnonograms.HeaderBarManager : Object { header_bar.pack_end (mode_switch); header_bar.pack_end (auto_solve_button); - title_label.bind_property ("text", controller, "game-name", BIDIRECTIONAL); + controller.bind_property ("game-name", title_label, "text", BIDIRECTIONAL); + controller.bind_property ("current-game-path", progress_stack, "tooltip-text", DEFAULT); + controller.bind_property ("game-grade", this, "game-grade", DEFAULT); view.bind_property ( "restart-destructive", @@ -204,7 +211,7 @@ public class Gnonograms.HeaderBarManager : Object { progress_indicator.text = text; } - public void hide_progress (Difficulty game_grade) { + public void hide_progress () { progress_stack.set_visible_child_name ("Title"); } diff --git a/src/View.vala b/src/View.vala index 31aba82..26fdf3d 100644 --- a/src/View.vala +++ b/src/View.vala @@ -56,8 +56,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public Cell? current_cell { get; set; } public Cell? previous_cell { get; set; } - public Difficulty generator_grade { get; set; } - public Difficulty game_grade { get; set; } + // public Difficulty generator_grade { get; set; } + // public Difficulty game_grade { get; set; } public bool readonly { get; set; default = false;} public bool restart_destructive { get; set; default = false;} @@ -232,8 +232,8 @@ public class Gnonograms.View : Gtk.ApplicationWindow { ); controller.notify["game-state"].connect (on_game_state_changed); - controller.notify["current-game-path"].connect (update_title); - notify["game-grade"].connect (update_title); + // controller.notify["current-game-path"].connect (update_title); + // notify["game-grade"].connect (update_title); notify["current-cell"].connect (() => { highlight_labels (previous_cell, false); @@ -320,16 +320,11 @@ public class Gnonograms.View : Gtk.ApplicationWindow { progress_timeout_id = 0; } - headerbar_manager.hide_progress (game_grade); + headerbar_manager.hide_progress (); update_all_labels_completeness (); } - public void update_title () { - warning ("View: update title %s", controller.game_name); - headerbar_manager.update_title (controller.current_game_path, game_grade); - } - public void on_can_go_changed (bool forward, bool back) { headerbar_manager.on_can_go_changed (forward, back); } @@ -430,9 +425,9 @@ public class Gnonograms.View : Gtk.ApplicationWindow { /** Action callbacks **/ private void action_restart () { controller.restart (); - if (controller.game_state == GameState.SETTING) { - game_grade = Difficulty.UNDEFINED; - } + // if (controller.game_state == GameState.SETTING) { + // game_grade = Difficulty.UNDEFINED; + // } } private void action_computer_solve () requires (controller.game_state == GameState.SETTING) { diff --git a/src/misc/Enums.vala b/src/misc/Enums.vala index 627b856..2f1195d 100644 --- a/src/misc/Enums.vala +++ b/src/misc/Enums.vala @@ -51,14 +51,6 @@ namespace Gnonograms { } } - // public enum CellState { - // UNKNOWN, - // EMPTY, - // FILLED, - // COMPLETED, - // UNDEFINED; - // } - public enum SolverState { ERROR = 0, CANCELLED = 1, From 9bba7f3349b73c6c5c7dc52561743ec44298c906 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 12 Dec 2024 16:53:39 +0000 Subject: [PATCH 140/142] Use dimensions property and notify --- src/Controller.vala | 62 ++++++++++++++++------------- src/HeaderBar/AppPopover.vala | 7 ++-- src/HeaderBar/HeaderBarManager.vala | 6 +-- src/HeaderBar/RestartButton.vala | 1 - src/Model.vala | 8 ++-- src/View.vala | 9 +---- src/dialogs/PreferencesDialog.vala | 26 ------------ src/services/Filereader.vala | 4 -- src/widgets/Cellgrid.vala | 8 ++-- src/widgets/Cluebox.vala | 9 +++-- 10 files changed, 57 insertions(+), 83 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index f1a5992..b7bf36d 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -26,22 +26,26 @@ public class Gnonograms.Controller : GLib.Object { } private static Controller? instance = null; + private static Gnonograms.App app = (Gnonograms.App) (Application.get_default ()); public Gtk.Window window { get { return (Gtk.Window)view;}} // Settings - public string saved_path { get; private set; default = ""; } // Where saved (not temporary file) + public string saved_path { get; set; } // Where saved (not temporary file) public Difficulty generator_grade { get; set; } // Target difficulty of generator. Set in AppPopover // Game details public GameState game_state { get; set; } // Whether solving or designing - public Difficulty game_grade { get; private set; default = UNDEFINED; } // Difficulty of the game, if known - public uint rows { get; set; } // Can be set be App Popover - public uint columns { get; set; } // Can be set be App Popover - public string game_name { get; set; default = _(UNTITLED_NAME); } // Can be set be App Popover - public string author { get; set; default = _("Unknown"); } //TODO Can be set be App Popover - - + public Difficulty game_grade { get; private set; } // Difficulty of the game, if known + public uint rows { get { return dimensions.height; } } // Can be set be App Popover + public uint columns { get { return dimensions.width; } } // Can be set be App Popover + public Dimensions dimensions { get; private set; } + public string game_name { get; set; } // Can be set be App Popover + public string author { get; set; } //TODO Can be set be App Popover + + // Game states + public bool restart_destructive { get; set; } + public bool can_go_back { get { return history.can_go_back; @@ -53,22 +57,20 @@ public class Gnonograms.Controller : GLib.Object { } } + // Private members private View view; private Model model; private Solver? solver; private SimpleRandomGameGenerator? generator; private Gnonograms.History history; - private string saved_games_folder; + private string saved_games_folder; // TODO Make user settable private string temporary_game_path; - private Gnonograms.App app = (Gnonograms.App) (Application.get_default ()); - private Dimensions dimensions { - get { - return { columns, rows }; - } - } + + + // Signals public signal void quit_app (); - public signal void dimensions_changed (uint rows, uint cols); + // public signal void dimensions_changed (uint rows, uint cols); private Controller () {} construct { @@ -91,7 +93,6 @@ public class Gnonograms.Controller : GLib.Object { saved_games_folder = Environment.get_user_special_dir (UserDirectory.DOCUMENTS); - saved_path = ""; temporary_game_path = Path.build_path ( Path.DIR_SEPARATOR_S, data_home_folder_current, @@ -99,17 +100,19 @@ public class Gnonograms.Controller : GLib.Object { ); saved_state.bind ("mode", this, "game-state", SettingsBindFlags.DEFAULT); - saved_state.bind ("current-game-path", this, "current-game-path", SettingsBindFlags.DEFAULT); + saved_state.bind ("current-game-path", this, "saved-path", SettingsBindFlags.DEFAULT); settings.bind ("grade", this, "generator-grade", SettingsBindFlags.DEFAULT); - notify["rows"].connect (on_dimensions_changed); - notify["columns"].connect (on_dimensions_changed); + notify["dimensions"].connect (on_dimensions_changed); } protected void set_model_and_view () { // Needs to be done after Controller construction complete as they need a controller instance model = Model.get_default (); + model.changed.connect (() => { + restart_destructive = !model.is_blank (game_state); + }); + view = View.get_default (); - view.close_request.connect (() => { return on_delete_request (); }); @@ -131,6 +134,7 @@ public class Gnonograms.Controller : GLib.Object { view.on_can_go_changed (forward, back); }); + restore_defaults (); restore_game.begin ((obj, res) => { if (!restore_game.end (res)) { @@ -143,17 +147,23 @@ public class Gnonograms.Controller : GLib.Object { } private void restore_defaults () { - rows = 10; - columns = 15; + var r = settings.get_uint ("rows"); + var c = settings.get_uint ("columns"); + dimensions = { c, r }; game_grade = Difficulty.UNDEFINED; game_state = GameState.SETTING; + saved_path = ""; + game_name = _(UNTITLED_NAME); + author = _("Unknown"); } private void on_dimensions_changed () { solver = new Solver (dimensions); game_name = _(UNTITLED_NAME); - dimensions_changed (rows, columns); + settings.set_uint ("rows", dimensions.height); + settings.set_uint ("columns", dimensions.width); + // dimensions_changed (rows, columns); } private void new_or_random_game () { @@ -192,7 +202,6 @@ public class Gnonograms.Controller : GLib.Object { critical ("Error saving game state"); } // Always quit for now - warning ("quitting after save state"); app.release (); app.quit (); }); @@ -417,8 +426,7 @@ public class Gnonograms.Controller : GLib.Object { return false; } else { // This will resize model and view as well - columns = reader.cols; - rows = reader.rows; + dimensions = { reader.cols, reader.rows }; } } else { reader.err_msg = (_("Dimensions missing")); diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 9bf4eb4..1f9d810 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -71,9 +71,10 @@ public class Gnonograms.AppPopover : Gtk.Popover { child = settings_box; - controller.bind_property ("columns", column_setting, "value", BIDIRECTIONAL); - controller.bind_property ("rows", row_setting, "value", BIDIRECTIONAL); - + // controller.bind_property ("columns", column_setting, "value", BIDIRECTIONAL); + // controller.bind_property ("rows", row_setting, "value", BIDIRECTIONAL); + // TODO Apply dumension settings on popdown + grade_setting.selected = controller.generator_grade; grade_setting.notify["selected"].connect (() => { controller.generator_grade = (Difficulty)(grade_setting.selected); diff --git a/src/HeaderBar/HeaderBarManager.vala b/src/HeaderBar/HeaderBarManager.vala index 7a3c40d..50c9ad8 100644 --- a/src/HeaderBar/HeaderBarManager.vala +++ b/src/HeaderBar/HeaderBarManager.vala @@ -156,13 +156,13 @@ public class Gnonograms.HeaderBarManager : Object { header_bar.pack_end (auto_solve_button); controller.bind_property ("game-name", title_label, "text", BIDIRECTIONAL); - controller.bind_property ("current-game-path", progress_stack, "tooltip-text", DEFAULT); + controller.bind_property ("saved-path", progress_stack, "tooltip-text", DEFAULT); controller.bind_property ("game-grade", this, "game-grade", DEFAULT); - view.bind_property ( + controller.bind_property ( "restart-destructive", restart_button, "restart-destructive", - BindingFlags.SYNC_CREATE + DEFAULT ); } diff --git a/src/HeaderBar/RestartButton.vala b/src/HeaderBar/RestartButton.vala index eb3784d..6d7337f 100644 --- a/src/HeaderBar/RestartButton.vala +++ b/src/HeaderBar/RestartButton.vala @@ -8,7 +8,6 @@ public bool restart_destructive { get; set; } construct { - restart_destructive = false; notify["restart-destructive"].connect (() => { if (restart_destructive) { add_css_class ("warn"); diff --git a/src/Model.vala b/src/Model.vala index 252b1c9..27b0bbe 100644 --- a/src/Model.vala +++ b/src/Model.vala @@ -46,15 +46,15 @@ public class Gnonograms.Model : GLib.Object { construct { make_data_arrays (); - controller.dimensions_changed.connect (on_dimensions_changed); + controller.notify["dimensions"].connect (on_dimensions_changed); controller.notify["game-state"].connect (() => { changed (); }); } - public void on_dimensions_changed (uint rows, uint cols) { - this.rows = rows; - this.cols = cols; + private void on_dimensions_changed () { + this.rows = controller.rows; + this.cols = controller.columns; make_data_arrays (); } diff --git a/src/View.vala b/src/View.vala index 26fdf3d..cf93dfe 100644 --- a/src/View.vala +++ b/src/View.vala @@ -56,10 +56,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { public Cell? current_cell { get; set; } public Cell? previous_cell { get; set; } - // public Difficulty generator_grade { get; set; } - // public Difficulty game_grade { get; set; } - public bool readonly { get; set; default = false;} - public bool restart_destructive { get; set; default = false;} + // public bool restart_destructive { get; set; default = false;} private Controller controller = Controller.get_default (); private Model model = Model.get_default (); @@ -232,8 +229,6 @@ public class Gnonograms.View : Gtk.ApplicationWindow { ); controller.notify["game-state"].connect (on_game_state_changed); - // controller.notify["current-game-path"].connect (update_title); - // notify["game-grade"].connect (update_title); notify["current-cell"].connect (() => { highlight_labels (previous_cell, false); @@ -262,7 +257,7 @@ public class Gnonograms.View : Gtk.ApplicationWindow { private void on_game_state_changed () { var gs = controller.game_state; update_all_labels_completeness (); - restart_destructive = !model.is_blank (gs); + // restart_destructive = !model.is_blank (gs); headerbar_manager.on_game_state_changed (gs); } diff --git a/src/dialogs/PreferencesDialog.vala b/src/dialogs/PreferencesDialog.vala index 4c1592d..bb2cd4e 100644 --- a/src/dialogs/PreferencesDialog.vala +++ b/src/dialogs/PreferencesDialog.vala @@ -9,32 +9,6 @@ public class Gnonograms.PreferencesDialog : Granite.Dialog { construct { set_default_size (400, 100); resizable = false; - // var grade_setting = new Gtk.DropDown.from_strings ( Difficulty.all_human ()); - // var grade_preference = new PreferenceRow (_("Degree of difficulty"), grade_setting); - - // var row_setting = new Gtk.SpinButton ( - // new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), - // 5.0, - // 0 - // ) { - // snap_to_ticks = true, - // orientation = Gtk.Orientation.HORIZONTAL, - // width_chars = 3, - // }; - - // var row_preference = new PreferenceRow (_("Rows"), row_setting); - - // var column_setting = new Gtk.SpinButton ( - // new Gtk.Adjustment (5.0, 5.0, 50.0, 5.0, 5.0, 5.0), - // 5.0, - // 0 - // ) { - // snap_to_ticks = true, - // orientation = Gtk.Orientation.HORIZONTAL, - // width_chars = 3 - // }; - - // var column_preference = new PreferenceRow (_("Columns"), column_setting); // //TODO Add Clue help switch var empty_color_dialog = new Gtk.ColorDialog () { diff --git a/src/services/Filereader.vala b/src/services/Filereader.vala index 15b8456..65a5b12 100644 --- a/src/services/Filereader.vala +++ b/src/services/Filereader.vala @@ -317,16 +317,12 @@ public class Gnonograms.Filereader : Object { } if (s.length >= 4) { - warning ("reading difficulty"); var grade = s[3].strip (); - warning ("read %s", grade); if (grade.length == 1 && grade[0].isdigit ()) { difficulty = (Difficulty)(int.parse (grade)); } else { difficulty = Difficulty.UNDEFINED; } - - warning ("got %s", difficulty.to_string ()); } return true; diff --git a/src/widgets/Cellgrid.vala b/src/widgets/Cellgrid.vala index 0adf941..10e54dc 100644 --- a/src/widgets/Cellgrid.vala +++ b/src/widgets/Cellgrid.vala @@ -146,7 +146,7 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { }); controller.notify["game-state"].connect (on_game_state_changed); - controller.dimensions_changed.connect (on_dimensions_changed); + controller.notify["dimensions"].connect (on_dimensions_changed); model.changed.connect (() => { if (!dirty) { @@ -159,9 +159,9 @@ public class Gnonograms.CellGrid : Gtk.DrawingArea { settings.changed["empty-color"].connect (set_colors); } - public void on_dimensions_changed (uint rows, uint cols) { - this.rows = rows; - this.cols = cols; + public void on_dimensions_changed () { + this.rows = controller.rows; + this.cols = controller.columns; queue_allocate (); } diff --git a/src/widgets/Cluebox.vala b/src/widgets/Cluebox.vala index 447f043..a399fd5 100644 --- a/src/widgets/Cluebox.vala +++ b/src/widgets/Cluebox.vala @@ -41,14 +41,12 @@ public class Gnonograms.ClueBox : Gtk.Widget { if (holds_column_clues) { hexpand = false; - // view.controller.notify ["columns"].connect (add_remove_clues); } else { vexpand = false; - // view.controller.notify ["rows"].connect (add_remove_clues); } notify["cell-size"].connect (update_size_request); - controller.dimensions_changed.connect (on_dimensions_changed); + controller.notify["dimensions"].connect (on_dimensions_changed); } private void update_size_request () { @@ -87,7 +85,10 @@ public class Gnonograms.ClueBox : Gtk.Widget { } } - public void on_dimensions_changed (uint rows, uint cols) { + public void on_dimensions_changed () { + var rows = controller.rows; + var cols = controller.columns; + var new_n_clues = holds_column_clues ? cols : rows; var new_n_cells = holds_column_clues ? rows : cols; From 896f73060e880f423646b97be15561f353d19aa2 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 12 Dec 2024 17:11:32 +0000 Subject: [PATCH 141/142] Sync dimensions with AppPopover --- src/Controller.vala | 15 ++++++++++----- src/HeaderBar/AppPopover.vala | 13 +++++++++---- src/widgets/Cluebox.vala | 6 +++++- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/Controller.vala b/src/Controller.vala index b7bf36d..2a72d77 100644 --- a/src/Controller.vala +++ b/src/Controller.vala @@ -31,7 +31,7 @@ public class Gnonograms.Controller : GLib.Object { public Gtk.Window window { get { return (Gtk.Window)view;}} // Settings - public string saved_path { get; set; } // Where saved (not temporary file) + public string saved_path { get; set; } // Where saved (not temporary file) public Difficulty generator_grade { get; set; } // Target difficulty of generator. Set in AppPopover // Game details @@ -45,7 +45,7 @@ public class Gnonograms.Controller : GLib.Object { // Game states public bool restart_destructive { get; set; } - + public bool can_go_back { get { return history.can_go_back; @@ -66,8 +66,6 @@ public class Gnonograms.Controller : GLib.Object { private string saved_games_folder; // TODO Make user settable private string temporary_game_path; - - // Signals public signal void quit_app (); // public signal void dimensions_changed (uint rows, uint cols); @@ -111,7 +109,7 @@ public class Gnonograms.Controller : GLib.Object { model.changed.connect (() => { restart_destructive = !model.is_blank (game_state); }); - + view = View.get_default (); view.close_request.connect (() => { return on_delete_request (); @@ -174,6 +172,13 @@ public class Gnonograms.Controller : GLib.Object { } } + public void change_dimensions (uint r, uint c) { + //TODO Check whether OK to change + if (r != rows || c != columns) { + dimensions = { c, r }; + } + } + public void change_mode (GameState mode) { switch (mode) { case SETTING: diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 1f9d810..8197887 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -71,13 +71,18 @@ public class Gnonograms.AppPopover : Gtk.Popover { child = settings_box; - // controller.bind_property ("columns", column_setting, "value", BIDIRECTIONAL); - // controller.bind_property ("rows", row_setting, "value", BIDIRECTIONAL); - // TODO Apply dumension settings on popdown - grade_setting.selected = controller.generator_grade; grade_setting.notify["selected"].connect (() => { controller.generator_grade = (Difficulty)(grade_setting.selected); }); + + controller.notify["dimensions"].connect (() => { + row_setting.value = controller.rows; + column_setting.value = controller.columns; + }); + + closed.connect (() => { + controller.change_dimensions ((uint) row_setting.value, (uint) column_setting.value); + }); } } diff --git a/src/widgets/Cluebox.vala b/src/widgets/Cluebox.vala index a399fd5..cb967dc 100644 --- a/src/widgets/Cluebox.vala +++ b/src/widgets/Cluebox.vala @@ -88,7 +88,7 @@ public class Gnonograms.ClueBox : Gtk.Widget { public void on_dimensions_changed () { var rows = controller.rows; var cols = controller.columns; - + var new_n_clues = holds_column_clues ? cols : rows; var new_n_cells = holds_column_clues ? rows : cols; @@ -109,6 +109,10 @@ public class Gnonograms.ClueBox : Gtk.Widget { clues.add (clue); clue.label.set_parent (this); } + } else { + foreach (var clue in clues) { + clue.text = "0"; + } } update_size_request (); From 6337949191a87c9d87a9226128f8b0139fb46977 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Thu, 12 Dec 2024 17:16:44 +0000 Subject: [PATCH 142/142] Sync popover on show/close --- src/HeaderBar/AppPopover.vala | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/HeaderBar/AppPopover.vala b/src/HeaderBar/AppPopover.vala index 8197887..35c1193 100644 --- a/src/HeaderBar/AppPopover.vala +++ b/src/HeaderBar/AppPopover.vala @@ -14,8 +14,6 @@ public class Gnonograms.AppPopover : Gtk.Popover { placeholder_text = _("Enter title of game here"), margin_top = 12, }; - title_entry.bind_property ("text", controller, "game-name", BIDIRECTIONAL); - var grade_setting = new Gtk.DropDown.from_strings ( Difficulty.all_human ()); var grade_preference = new PreferenceRow (_("Degree of difficulty"), grade_setting); @@ -71,18 +69,17 @@ public class Gnonograms.AppPopover : Gtk.Popover { child = settings_box; - grade_setting.selected = controller.generator_grade; - grade_setting.notify["selected"].connect (() => { - controller.generator_grade = (Difficulty)(grade_setting.selected); - }); - - controller.notify["dimensions"].connect (() => { + show.connect (() => { + title_entry.text = controller.game_name; row_setting.value = controller.rows; column_setting.value = controller.columns; + grade_setting.selected = controller.generator_grade; }); closed.connect (() => { + controller.game_name = title_entry.text; controller.change_dimensions ((uint) row_setting.value, (uint) column_setting.value); + controller.generator_grade = (Difficulty)(grade_setting.selected); }); } }