1/**
2 * Copyright (c) 2017-2020 Alecaddd (https://alecaddd.com)
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301 USA
18 *
19 * Authored by: Francisco Altoe
20 * Authored by: Alessandro "Alecaddd" Castellani <castellani.ale@gmail.com>
21 */
22
23/**
24 * Dialog subclass used to prompt the user to provide the required query parameters
25 */
26public class Sequeler.Widgets.QueryParamsDialog : Gtk.Dialog {
27    public weak Sequeler.Window window { get; construct; }
28
29    public Sequeler.Layouts.Views.Query parent_view { get; construct; }
30    public string query { get; construct; }
31    public Gda.Statement statement { get; construct; }
32    public Gda.Set params { get; construct; }
33    private Gee.ArrayList<Gtk.Widget> entries;
34
35    private Sequeler.Partials.ResponseMessage response_msg;
36
37    enum Action {
38        RUN_QUERY,
39        CANCEL
40    }
41
42    public QueryParamsDialog (
43        Sequeler.Window? parent,
44        Sequeler.Layouts.Views.Query view,
45        string query,
46        Gda.Statement statement,
47        Gda.Set? @params
48    ) {
49        Object (
50            border_width: 6,
51            deletable: false,
52            resizable: true,
53            title: _("Query parameters"),
54            transient_for: parent,
55            window: parent,
56            parent_view: view,
57            query: query,
58            statement: statement,
59            params: @params
60        );
61    }
62
63    construct {
64        build_content ();
65        response.connect (on_response);
66    }
67
68    private void build_content () {
69        default_width = 500;
70        var content = get_content_area ();
71
72        var form_grid = new Gtk.Grid ();
73        form_grid.margin = 6;
74        form_grid.row_spacing = 12;
75        form_grid.column_spacing = 12;
76
77        entries = new Gee.ArrayList<Gtk.Widget> ();
78
79        for (int i = 0; ; i++) {
80            Gda.Holder? holder = params.get_nth_holder (i);
81            if (holder == null) {
82                break;
83            }
84            var holder_id = holder.get_id ();
85
86            var label = new Gtk.Label (holder_id + ":");
87            form_grid.attach (label, 0, i, 1, 1);
88            var entry = entry_for_holder (holder);
89            form_grid.attach (entry, 1, i, 1, 1);
90            entries.add (entry);
91        }
92
93        var scrolled_window = new Gtk.ScrolledWindow (null, null);
94        scrolled_window.add (form_grid);
95
96        int main_window_width, main_window_height;
97        parent_view.window.get_size (out main_window_width, out main_window_height);
98
99        // Prevent the scrolled window from growing bigger than the main window itself.
100        scrolled_window.set_max_content_height (main_window_height / 2);
101        scrolled_window.set_max_content_width (main_window_width);
102        scrolled_window.set_propagate_natural_width (true);
103        scrolled_window.set_propagate_natural_height (true);
104
105        response_msg = new Sequeler.Partials.ResponseMessage ();
106
107        content.add (scrolled_window);
108        content.add (response_msg);
109
110        var cancel_button = new Gtk.Button.with_label (_("Cancel"));
111        add_action_widget (cancel_button, Action.CANCEL);
112
113        var run_button = new Sequeler.Partials.RunQueryButton ();
114        add_action_widget (run_button, Action.RUN_QUERY);
115    }
116
117    private Gtk.Widget entry_for_holder (Gda.Holder holder) {
118        Type holder_g_type = holder.get_g_type ();
119        switch (holder_g_type) {
120            case Type.BOOLEAN:
121                return new Gtk.Switch ();
122            case Type.INT:
123            case Type.UINT:
124                var widget = new Partials.ParamEntry (this, Gtk.InputPurpose.DIGITS);
125                return widget;
126            case Type.FLOAT:
127            case Type.DOUBLE:
128                var widget = new Partials.ParamEntry (this, Gtk.InputPurpose.NUMBER);
129                return widget;
130            default:
131                return new Partials.ParamEntry (this);
132        }
133    }
134
135    /**
136     * Takes the parse result and update the widgets style and the holder value.
137     */
138    private bool store_parsed_value (
139        bool parse_result,
140        Value parsed_value,
141        Gda.Holder holder,
142        Gtk.Entry entry
143    ) {
144        entry.get_style_context ().remove_class ("error");
145
146        if (!parse_result) {
147            entry.get_style_context ().add_class ("error");
148            return false;
149        }
150
151        try {
152            holder.set_value (parsed_value);
153        } catch (Error e) {
154            write_response (e.message);
155            entry.get_style_context ().add_class ("error");
156            return false;
157        }
158
159        return true;
160    }
161
162    private bool set_value_for_holder (Gda.Holder holder, Gtk.Widget widget) {
163        Type holder_g_type = holder.get_g_type ();
164        if (holder_g_type == Type.BOOLEAN) {
165            Gtk.Switch switch = widget as Gtk.Switch;
166            if (switch == null) {
167                return false;
168            }
169
170            try {
171                holder.set_value (switch.get_active ());
172            } catch (Error ex) {
173                return false;
174            }
175
176            return true;
177        } else {
178            Gtk.Entry entry = widget as Gtk.Entry;
179            string text = entry.get_text ();
180
181            bool parse_result = true;
182            Value parsed_value;
183
184            if (holder_g_type == Type.INT) {
185                // TODO: replace this with the following once we upgrade to a newer valac
186                // parse_result = int.try_parse (text, out parsed_value);
187                parsed_value = int.parse (text);
188            } else if (holder_g_type == Type.UINT) {
189                // TODO: replace this with the following once we upgrade to a newer valac
190                // parse_result = uint.try_parse (text, out parsed_value);
191                parsed_value = int.parse (text);
192            } else if (holder_g_type == Type.FLOAT) {
193                // TODO: replace this with the following once we upgrade to a newer valac
194                //  parse_result = float.try_parse (text, out parsed_value);
195                parsed_value = double.parse (text);
196            } else if (holder_g_type == Type.DOUBLE) {
197                // TODO: replace this with the following once we upgrade to a newer valac
198                //  parse_result = double.try_parse (text, out parsed_value);
199                parsed_value = double.parse (text);
200            } else {
201                parsed_value = text;
202            }
203
204            return store_parsed_value (parse_result, parsed_value, holder, entry);
205        }
206    }
207
208    private bool get_param_values () {
209        bool validation_result = true;
210        for (int i = 0; ; i++) {
211            Gda.Holder? holder = params.get_nth_holder (i);
212            if (holder == null) {
213                break;
214            }
215            validation_result &= set_value_for_holder (holder, entries[i]);
216        }
217
218        return validation_result;
219    }
220
221    private void on_response (Gtk.Dialog source, int response_id) {
222        switch (response_id) {
223            case Action.RUN_QUERY:
224                run_query ();
225                break;
226            case Action.CANCEL:
227                destroy ();
228                break;
229        }
230    }
231
232    public void run_query () {
233        if (!get_param_values ()) {
234            return;
235        }
236
237        parent_view.run_query_statement (query, statement, params);
238        destroy ();
239    }
240
241    private void write_response (string? response_text) {
242        response_msg.label = response_text;
243    }
244}
245