Commit 7bd12185 authored by Morten Welinder's avatar Morten Welinder

solver: improve undo/redo.

parent 20b73c3c
......@@ -22,6 +22,8 @@ Morten:
* Use external solver program.
* Allow constants on solver constraints' rhs. [#369728]
* Fix extreme case for R.PGAMMA.
* Fix solver undo/redo.
* Make solver parameter changes always persist. [#440664]
--------------------------------------------------------------------------
Gnumeric 1.9.15
......
......@@ -6321,7 +6321,7 @@ cmd_solver_finalize (GObject *cmd)
}
gboolean
cmd_solver (WorkbookControl *wbc, GOUndo *undo, GOUndo *redo)
cmd_solver (WorkbookControl *wbc, const char *txt, GOUndo *undo, GOUndo *redo)
{
CmdSolver *me;
......@@ -6332,7 +6332,7 @@ cmd_solver (WorkbookControl *wbc, GOUndo *undo, GOUndo *redo)
me->cmd.sheet = NULL;
me->cmd.size = 1;
me->cmd.cmd_descriptor = g_strdup_printf (_("Solver"));
me->cmd.cmd_descriptor = g_strdup (txt);
me->undo = undo;
me->redo = redo;
......
......@@ -128,7 +128,8 @@ gboolean cmd_text_to_columns (WorkbookControl *wbc,
GnmRange const *target, Sheet *target_sheet,
GnmCellRegion *content);
gboolean cmd_solver (WorkbookControl *wbc, GOUndo *undo, GOUndo *redo);
gboolean cmd_solver (WorkbookControl *wbc, const char *text,
GOUndo *undo, GOUndo *redo);
gboolean cmd_goal_seek (WorkbookControl *wbc,
GnmCell *cell, GnmValue *ov, GnmValue *nv);
......
......@@ -75,7 +75,7 @@ typedef struct {
GtkComboBox *type_combo;
GtkComboBox *algorithm_combo;
GtkTreeView *constraint_list;
GnmSolverConstraint *constr;
GnmSolverConstraint *constr;
GtkWidget *warning_dialog;
struct {
......@@ -92,6 +92,8 @@ typedef struct {
Sheet *sheet;
WBCGtk *wbcg;
GnmSolverParameters *orig_params;
} SolverState;
......@@ -291,6 +293,8 @@ fill_algorithm_combo (SolverState *state, GnmSolverModelType type)
GtkListStore *store =
gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
GSList *solvers, *l;
int sel = 0, i;
GnmSolverParameters *param =state->sheet->solver_parameters;
gtk_combo_box_set_model (state->algorithm_combo, GTK_TREE_MODEL (store));
......@@ -301,17 +305,20 @@ fill_algorithm_combo (SolverState *state, GnmSolverModelType type)
continue;
l = g_slist_prepend (l, entry);
}
solvers = l;
solvers = g_slist_reverse (l);
gtk_widget_set_sensitive (GTK_WIDGET (state->solve_button),
solvers != NULL);
if (!solvers)
return FALSE;
for (l = solvers; l; l = l->next) {
for (l = solvers, i = 0; l; l = l->next, i++) {
GnmSolverFactory *factory = l->data;
GtkTreeIter iter;
if (param->options.algorithm == factory)
sel = i;
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter,
0, factory->name,
......@@ -320,7 +327,7 @@ fill_algorithm_combo (SolverState *state, GnmSolverModelType type)
}
g_slist_free (solvers);
gtk_combo_box_set_active (state->algorithm_combo, 0);
gtk_combo_box_set_active (state->algorithm_combo, sel);
return TRUE;
}
......@@ -346,11 +353,123 @@ cb_dialog_model_type_clicked (G_GNUC_UNUSED GtkWidget *button,
}
}
static void
free_state (SolverState *state)
{
if (state->orig_params)
g_object_unref (state->orig_params);
g_free (state);
}
static GOUndo *
set_params (Sheet *sheet, GnmSolverParameters *params)
{
return go_undo_binary_new
(sheet, g_object_ref (params),
(GOUndoBinaryFunc)gnm_sheet_set_solver_params,
NULL, g_object_unref);
}
#define GET_BOOL_ENTRY(name_, field_) \
do { \
GtkWidget *w_ = glade_xml_get_widget (state->gui, (name_)); \
param->field_ = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w_)); \
} while (0)
static void
extract_settings (SolverState *state)
{
GnmSolverParameters *param = state->sheet->solver_parameters;
GtkTreeIter iter;
GnmCell *target_cell;
GnmValue *target_range;
GnmValue *input_range;
GnmSolverFactory *factory = NULL;
gboolean dual_program;
target_range = gnm_expr_entry_parse_as_value (state->target_entry,
state->sheet);
input_range = gnm_expr_entry_parse_as_value (state->change_cell_entry,
state->sheet);
gnm_solver_param_set_input (param, input_range);
gnm_solver_param_set_target (param,
target_range
? &target_range->v_range.cell.a
: NULL);
target_cell = gnm_solver_param_get_target_cell (param);
param->problem_type =
gnumeric_glade_group_value (state->gui, problem_type_group);
param->options.model_type =
gnumeric_glade_group_value (state->gui, model_type_group);
gtk_combo_box_get_active_iter (state->algorithm_combo, &iter);
gtk_tree_model_get (gtk_combo_box_get_model (state->algorithm_combo),
&iter, 1, &factory, -1);
param->options.algorithm = factory;
param->options.automatic_scaling = gtk_toggle_button_get_active
(GTK_TOGGLE_BUTTON (glade_xml_get_widget
(state->gui, "autoscale_button")));
param->options.max_iter = gtk_spin_button_get_value
(GTK_SPIN_BUTTON (state->max_iter_entry));
param->options.max_time_sec = gtk_spin_button_get_value
(GTK_SPIN_BUTTON (state->max_time_entry));
GET_BOOL_ENTRY ("non_neg_button", options.assume_non_negative);
GET_BOOL_ENTRY ("all_int_button", options.assume_discrete);
GET_BOOL_ENTRY ("answer", options.answer_report);
GET_BOOL_ENTRY ("sensitivity", options.sensitivity_report);
GET_BOOL_ENTRY ("limits", options.limits_report);
GET_BOOL_ENTRY ("performance", options.performance_report);
GET_BOOL_ENTRY ("program", options.program_report);
g_free (param->options.scenario_name);
param->options.scenario_name = g_strdup
(gtk_entry_get_text (GTK_ENTRY (state->scenario_name_entry)));
GET_BOOL_ENTRY ("optimal_scenario", options.add_scenario);
dual_program = FALSE;
param->options.dual_program_report = dual_program;
value_release (target_range);
}
#undef GET_BOOL_ENTRY
static void
check_for_changed_options (SolverState *state)
{
Sheet *sheet = state->sheet;
if (!gnm_solver_param_equal (sheet->solver_parameters,
state->orig_params)) {
GOUndo *undo = set_params (sheet, state->orig_params);
GOUndo *redo = set_params (sheet, sheet->solver_parameters);
cmd_solver (WORKBOOK_CONTROL (state->wbcg),
_("Changing solver parameters"),
undo, redo);
g_object_unref (state->orig_params);
state->orig_params =
gnm_solver_param_dup (sheet->solver_parameters,
sheet);
}
}
static void
cb_dialog_solver_destroy (SolverState *state)
{
g_return_if_fail (state != NULL);
extract_settings (state);
check_for_changed_options (state);
if (state->gui != NULL) {
g_object_unref (G_OBJECT (state->gui));
state->gui = NULL;
......@@ -359,7 +478,6 @@ cb_dialog_solver_destroy (SolverState *state)
wbcg_edit_finish (state->wbcg, WBC_EDIT_REJECT, NULL);
state->dialog = NULL;
g_free (state);
}
static void
......@@ -621,7 +739,9 @@ run_solver (SolverState *state, GnmSolverParameters *param)
gnm_solver_store_result (sol);
redo = clipboard_copy_range_undo (sr.sheet, &sr.range);
cmd_solver (WORKBOOK_CONTROL (state->wbcg), undo, redo);
cmd_solver (WORKBOOK_CONTROL (state->wbcg),
_("Running solver"),
undo, redo);
res = g_object_ref (sol->result);
undo = redo = NULL;
break;
......@@ -670,90 +790,16 @@ static void
cb_dialog_solve_clicked (G_GNUC_UNUSED GtkWidget *button,
SolverState *state)
{
GnmSolverResult *res;
GnmValue *target_range;
GnmValue *input_range;
gboolean answer, sensitivity, limits, performance;
gboolean program, dual_program;
GnmSolverResult *res;
GnmSolverParameters *param = state->sheet->solver_parameters;
GError *err = NULL;
GnmSolverParameters *param;
GtkTreeIter iter;
GnmCell *target_cell;
GnmSolverFactory *factory = NULL;
param = state->sheet->solver_parameters;
if (state->warning_dialog != NULL)
if (state->warning_dialog != NULL) {
gtk_widget_destroy (state->warning_dialog);
state->warning_dialog = NULL;
}
target_range = gnm_expr_entry_parse_as_value (state->target_entry,
state->sheet);
input_range = gnm_expr_entry_parse_as_value (state->change_cell_entry,
state->sheet);
gnm_solver_param_set_input (param, input_range);
gnm_solver_param_set_target (param,
target_range
? &target_range->v_range.cell.a
: NULL);
target_cell = gnm_solver_param_get_target_cell (param);
param->problem_type =
gnumeric_glade_group_value (state->gui, problem_type_group);
param->options.model_type =
gnumeric_glade_group_value (state->gui, model_type_group);
gtk_combo_box_get_active_iter (state->algorithm_combo, &iter);
gtk_tree_model_get (gtk_combo_box_get_model (state->algorithm_combo),
&iter, 1, &factory, -1);
param->options.algorithm = factory;
param->options.assume_non_negative = gtk_toggle_button_get_active
(GTK_TOGGLE_BUTTON (glade_xml_get_widget (state->gui,
"non_neg_button")));
param->options.assume_discrete = gtk_toggle_button_get_active
(GTK_TOGGLE_BUTTON (glade_xml_get_widget (state->gui,
"all_int_button")));
param->options.automatic_scaling = gtk_toggle_button_get_active
(GTK_TOGGLE_BUTTON (glade_xml_get_widget
(state->gui, "autoscale_button")));
param->options.max_iter = gtk_spin_button_get_value
(GTK_SPIN_BUTTON (state->max_iter_entry));
param->options.max_time_sec = gtk_spin_button_get_value
(GTK_SPIN_BUTTON (state->max_time_entry));
answer = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
glade_xml_get_widget (state->gui, "answer")));
param->options.answer_report = answer;
sensitivity = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
glade_xml_get_widget (state->gui, "sensitivity")));
param->options.sensitivity_report = sensitivity;
limits = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
glade_xml_get_widget (state->gui, "limits")));
param->options.limits_report = limits;
performance = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
glade_xml_get_widget (state->gui, "performance")));
param->options.performance_report = performance;
program = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
glade_xml_get_widget (state->gui, "program")));
param->options.program_report = program;
g_free (param->options.scenario_name);
param->options.scenario_name = g_strdup
(gtk_entry_get_text (GTK_ENTRY (state->scenario_name_entry)));
param->options.add_scenario = gtk_toggle_button_get_active
(GTK_TOGGLE_BUTTON (glade_xml_get_widget (state->gui,
"optimal_scenario")));
dual_program = FALSE;
param->options.dual_program_report = dual_program;
extract_settings (state);
if (!gnm_solver_param_valid (param, &err)) {
GtkWidget *top = gtk_widget_get_toplevel (state->dialog);
......@@ -762,6 +808,8 @@ cb_dialog_solve_clicked (G_GNUC_UNUSED GtkWidget *button,
goto out;
}
check_for_changed_options (state);
res = run_solver (state, param);
workbook_recalc (state->sheet->workbook);
......@@ -786,11 +834,28 @@ cb_dialog_solve_clicked (G_GNUC_UNUSED GtkWidget *button,
}
out:
value_release (target_range);
if (err)
g_error_free (err);
}
static void
bool_entry_changed (GtkToggleButton *tb, SolverState *state)
{
GnmSolverParameters *param = state->sheet->solver_parameters;
gulong offset =
GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (tb), "offset"));
gboolean *pb =
(gboolean *)((char *)param + offset);
*pb = gtk_toggle_button_get_active (tb);
}
#define INIT_BOOL_ENTRY(name_, field_) \
do { \
GtkWidget *w_ = glade_xml_get_widget (state->gui, (name_)); \
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w_), \
param->field_); \
} while (0)
/**
* dialog_init:
......@@ -802,8 +867,8 @@ cb_dialog_solve_clicked (G_GNUC_UNUSED GtkWidget *button,
static gboolean
dialog_init (SolverState *state)
{
GtkTable *table;
GnmSolverParameters *param;
GtkTable *table;
GnmSolverParameters *param;
GtkCellRenderer *renderer;
GtkListStore *store;
GtkTreeViewColumn *column;
......@@ -904,15 +969,19 @@ dialog_init (SolverState *state)
/* Options */
state->max_iter_entry = glade_xml_get_widget (state->gui,
"max_iter_entry");
if (state->max_iter_entry == NULL)
return TRUE;
gtk_entry_set_text (GTK_ENTRY (state->max_iter_entry), "200");
{
char *txt = g_strdup_printf ("%d", param->options.max_iter);
gtk_entry_set_text (GTK_ENTRY (state->max_iter_entry), txt);
g_free (txt);
}
state->max_time_entry = glade_xml_get_widget (state->gui,
"max_time_entry");
if (state->max_time_entry == NULL)
return TRUE;
gtk_entry_set_text (GTK_ENTRY (state->max_time_entry), "30");
{
char *txt = g_strdup_printf ("%d", param->options.max_time_sec);
gtk_entry_set_text (GTK_ENTRY (state->max_time_entry), txt);
g_free (txt);
}
/* lhs_entry */
table = GTK_TABLE (glade_xml_get_widget (state->gui, "edit-table"));
......@@ -1014,28 +1083,13 @@ dialog_init (SolverState *state)
g_free (str);
}
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
glade_xml_get_widget(state->gui, "non_neg_button")),
param->options.assume_non_negative);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
glade_xml_get_widget(state->gui, "all_int_button")),
param->options.assume_discrete);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
glade_xml_get_widget(state->gui, "answer")),
param->options.answer_report);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
glade_xml_get_widget(state->gui, "sensitivity")),
param->options.sensitivity_report);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
glade_xml_get_widget(state->gui, "limits")),
param->options.limits_report);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
glade_xml_get_widget(state->gui, "performance")),
param->options.performance_report);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
glade_xml_get_widget(state->gui, "program")),
param->options.program_report);
INIT_BOOL_ENTRY ("non_neg_button", options.assume_non_negative);
INIT_BOOL_ENTRY ("all_int_button", options.assume_discrete);
INIT_BOOL_ENTRY ("answer", options.answer_report);
INIT_BOOL_ENTRY ("sensitivity", options.sensitivity_report);
INIT_BOOL_ENTRY ("limits", options.limits_report);
INIT_BOOL_ENTRY ("performance", options.performance_report);
INIT_BOOL_ENTRY ("program", options.program_report);
input = gnm_solver_param_get_input (param);
if (input != NULL)
......@@ -1045,6 +1099,7 @@ dialog_init (SolverState *state)
if (target_cell)
gnm_expr_entry_load_from_text (state->target_entry,
cell_name (target_cell));
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
glade_xml_get_widget(state->gui, "max_button")),
param->problem_type == GNM_SOLVER_MAXIMIZE);
......@@ -1066,8 +1121,6 @@ dialog_init (SolverState *state)
state->scenario_name_entry = glade_xml_get_widget
(state->gui, "scenario_name_entry");
if (state->scenario_name_entry == NULL)
return TRUE;
gtk_entry_set_text (GTK_ENTRY (state->scenario_name_entry),
param->options.scenario_name);
......@@ -1079,8 +1132,14 @@ dialog_init (SolverState *state)
/* dialog */
wbc_gtk_attach_guru (state->wbcg, state->dialog);
g_signal_connect_swapped (G_OBJECT (state->dialog),
"destroy",
G_CALLBACK (cb_dialog_solver_destroy),
state);
g_object_set_data_full (G_OBJECT (state->dialog),
"state", state, (GDestroyNotify) cb_dialog_solver_destroy);
"state", state,
(GDestroyNotify)free_state);
return FALSE;
}
......@@ -1106,11 +1165,13 @@ dialog_solver (WBCGtk *wbcg, Sheet *sheet)
state->wbcg = wbcg;
state->sheet = sheet;
state->warning_dialog = NULL;
state->orig_params = gnm_solver_param_dup (sheet->solver_parameters,
sheet);
if (dialog_init (state)) {
go_gtk_notice_dialog (wbcg_toplevel (wbcg), GTK_MESSAGE_ERROR,
_("Could not create the Solver dialog."));
g_free (state);
free_state (state);
return;
}
......
......@@ -2889,6 +2889,8 @@ gnm_expr_top_equal (GnmExprTop const *te1, GnmExprTop const *te2)
{
if (te1 == te2)
return TRUE;
if (te1 == NULL || te2 == NULL)
return FALSE;
g_return_val_if_fail (IS_GNM_EXPR_TOP (te1), FALSE);
g_return_val_if_fail (IS_GNM_EXPR_TOP (te2), FALSE);
......
......@@ -5768,6 +5768,16 @@ gnm_sheet_get_size2 (Sheet const *sheet, Workbook const *wb)
: workbook_get_sheet_size (wb);
}
void
gnm_sheet_set_solver_params (Sheet *sheet, GnmSolverParameters *param)
{
g_return_if_fail (IS_SHEET (sheet));
g_return_if_fail (GNM_IS_SOLVER_PARAMETERS (param));
g_object_ref (param);
g_object_unref (sheet->solver_parameters);
sheet->solver_parameters = param;
}
GHashTable *
gnm_sheet_get_sort_setups (Sheet *sheet)
......
......@@ -279,6 +279,8 @@ gboolean sheet_range_has_heading (Sheet const *sheet, GnmRange const *src,
void gnm_sheet_foreach_name (Sheet const *sheet, GHFunc func, gpointer data);
void gnm_sheet_set_solver_params (Sheet *sheet, GnmSolverParameters *param);
GHashTable *gnm_sheet_get_sort_setups (Sheet *sheet);
void gnm_sheet_add_sort_setup (Sheet *sheet, char *key, gpointer setup);
gconstpointer gnm_sheet_find_sort_setup (Sheet *sheet, char const *key);
......
......@@ -120,11 +120,21 @@ gnm_solver_constraint_dup (GnmSolverConstraint *c, Sheet *sheet)
{
GnmSolverConstraint *res = gnm_solver_constraint_new (sheet);
res->type = c->type;
dependent_managed_set_expr (&res->lhs, res->lhs.texpr);
dependent_managed_set_expr (&res->rhs, res->lhs.texpr);
dependent_managed_set_expr (&res->lhs, c->lhs.texpr);
dependent_managed_set_expr (&res->rhs, c->rhs.texpr);
return res;
}
gboolean
gnm_solver_constraint_equal (GnmSolverConstraint const *a,
GnmSolverConstraint const *b)
{
return (a->type == b->type &&
gnm_expr_top_equal (a->lhs.texpr, b->lhs.texpr) &&
(!gnm_solver_constraint_has_rhs (a) ||
gnm_expr_top_equal (a->rhs.texpr, b->rhs.texpr)));
}
gboolean
gnm_solver_constraint_has_rhs (GnmSolverConstraint const *c)
{
......@@ -403,6 +413,45 @@ gnm_solver_param_dup (GnmSolverParameters *src, Sheet *new_sheet)
return dst;
}
gboolean
gnm_solver_param_equal (GnmSolverParameters const *a,
GnmSolverParameters const *b)
{
GSList *la, *lb;
if (a->sheet != b->sheet ||
a->problem_type != b->problem_type ||
!gnm_expr_top_equal (a->target.texpr, b->target.texpr) ||
!gnm_expr_top_equal (a->input.texpr, b->input.texpr) ||
a->options.max_time_sec != b->options.max_time_sec ||
a->options.max_iter != b->options.max_iter ||
a->options.algorithm != b->options.algorithm ||
a->options.model_type != b->options.model_type ||
a->options.assume_non_negative != b->options.assume_non_negative ||
a->options.assume_discrete != b->options.assume_discrete ||
a->options.automatic_scaling != b->options.automatic_scaling ||
a->options.show_iter_results != b->options.show_iter_results ||
a->options.answer_report != b->options.answer_report ||
a->options.sensitivity_report != b->options.sensitivity_report ||
a->options.limits_report != b->options.limits_report ||
a->options.performance_report != b->options.performance_report ||
a->options.program_report != b->options.program_report ||
a->options.dual_program_report != b->options.dual_program_report ||
a->options.add_scenario != b->options.add_scenario ||
strcmp (a->options.scenario_name, b->options.scenario_name))
return FALSE;
for (la = a->constraints, lb = b->constraints;
la && lb;
la = la->next, lb = lb->next) {
GnmSolverConstraint *ca = la->data;
GnmSolverConstraint *cb = lb->data;
if (!gnm_solver_constraint_equal (ca, cb))
return FALSE;
}
return la == lb;
}
GnmValue const *
gnm_solver_param_get_input (GnmSolverParameters const *sp)
{
......@@ -453,17 +502,19 @@ gnm_solver_param_get_input_cells (GnmSolverParameters const *sp)
void
gnm_solver_param_set_target (GnmSolverParameters *sp, GnmCellRef const *cr)
{
GnmCellRef cr2 = *cr;
GnmExprTop const *texpr;
/* Make reference absolute to avoid tracking problems on row/col
insert. */
cr2.row_relative = FALSE;
cr2.col_relative = FALSE;
texpr = gnm_expr_top_new (gnm_expr_new_cellref (&cr2));
dependent_managed_set_expr (&sp->target, texpr);
gnm_expr_top_unref (texpr);
if (cr) {
GnmExprTop const *texpr;
GnmCellRef cr2 = *cr;
/* Make reference absolute to avoid tracking problems on row/col
insert. */
cr2.row_relative = FALSE;
cr2.col_relative = FALSE;
texpr = gnm_expr_top_new (gnm_expr_new_cellref (&cr2));
dependent_managed_set_expr (&sp->target, texpr);
gnm_expr_top_unref (texpr);
} else
dependent_managed_set_expr (&sp->target, NULL);
}
const GnmCellRef *
......@@ -565,9 +616,12 @@ gnm_solver_param_constructor (GType type,
dependent_managed_init (&sp->target, sp->sheet);
dependent_managed_init (&sp->input, sp->sheet);
sp->options.model_type = GNM_SOLVER_LP;
sp->options.model_type = GNM_SOLVER_LP;
sp->options.max_iter = 100;
sp->options.max_time_sec = 30;
sp->options.assume_non_negative = TRUE;
sp->options.scenario_name = g_strdup ("Optimal");
sp->options.algorithm = g_slist_nth_data (gnm_solver_db_get (), 0);
return obj;
}
......@@ -1128,8 +1182,16 @@ gnm_sub_solver_spawn (GnmSubSolver *subsol,
if (io_stdout == NULL)
spflags |= G_SPAWN_STDOUT_TO_DEV_NULL;
if (debug_solver ())
g_printerr ("Spawning %s\n", argv[0]);
if (debug_solver ()) {
GString *msg = g_string_new ("Spawning");
int i;
for (i = 0; argv[i]; i++) {
g_string_append_c (msg, ' ');
g_string_append (msg, argv[i]);
}
g_printerr ("%s\n", msg->str);
g_string_free (msg, TRUE);
}
ok = g_spawn_async_with_pipes
(g_get_home_dir (), /* PWD */
......@@ -1306,13 +1368,20 @@ gnm_solver_factory_create (GnmSolverFactory *factory,
return factory->creator (factory, param);
}
static int
cb_compare_factories (GnmSolverFactory *a, GnmSolverFactory *b)
{
return go_utf8_collate_casefold (a->name, b->name);
}
void
gnm_solver_db_register (GnmSolverFactory *factory)
{
if (debug_solver ())
g_printerr ("Registering %s\n", factory->id);
g_object_ref (factory);
solvers = g_slist_prepend (solvers, factory);
solvers = g_slist_insert_sorted (solvers, factory,
(GCompareFunc)cb_compare_factories);
}
void
......
......@@ -70,6 +70,8 @@ GnmSolverConstraint *gnm_solver_constraint_new (Sheet *sheet);
void gnm_solver_constraint_free (GnmSolverConstraint *c);
GnmSolverConstraint *gnm_solver_constraint_dup (GnmSolverConstraint *c,
Sheet *sheet);
gboolean gnm_solver_constraint_equal (GnmSolverConstraint const *a,
GnmSolverConstraint const *b);
void gnm_solver_constraint_set_old (GnmSolverConstraint *c,
GnmSolverConstraintType type,
......@@ -119,6 +121,7 @@ typedef struct {
#define GNM_SOLVER_PARAMETERS_TYPE (gnm_solver_param_get_type ())
#define GNM_SOLVER_PARAMETERS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GNM_SOLVER_PARAMETERS_TYPE, GnmSolverParameters))
#define GNM_IS_SOLVER_PARAMETERS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GNM_SOLVER_PARAMETERS_TYPE))
struct GnmSolverParameters_ {
GObject parent;
......@@ -153,6 +156,9 @@ GnmSolverParameters *gnm_solver_param_new (Sheet *sheet);
GnmSolverParameters *gnm_solver_param_dup (GnmSolverParameters *src_param,
Sheet *new_sheet);
gboolean gnm_solver_param_equal (GnmSolverParameters const *a,
GnmSolverParameters const *b);
GnmValue const *gnm_solver_param_get_input (GnmSolverParameters const *sp);
void gnm_solver_param_set_input (GnmSolverParameters *sp, GnmValue *v);
GSList *gnm_solver_param_get_input_cells (GnmSolverParameters const *sp);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment