1/*
2 * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
3 * Copyright (C) 2008-2012 Robert Ancell
4 *
5 * This program is free software: you can redistribute it and/or modify it under
6 * the terms of the GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option) any later
8 * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
9 * license.
10 */
11
12public class Calculator : Gtk.Application
13{
14    private Settings settings;
15    private MathWindow last_opened_window;
16    int n_math_windows = 0;
17    private MathPreferencesDialog preferences_dialog;
18    private Gtk.ShortcutsWindow shortcuts_window;
19    private static string program_name = null;
20    private static string equation_string = null;
21    private static string mode_string = null;
22
23    private const OptionEntry[] option_entries = {
24        /* Translators: Do not translate possible mode names basic, advanced, financial, programming and keyboard */
25        { "mode", 'm', 0, OptionArg.STRING, ref mode_string, N_("Start in given mode (basic, advanced, financial, programming, keyboard)"), "mode" },
26        { "solve", 's', 0, OptionArg.STRING, null, N_("Solve given equation"), "equation" },
27        { "equation", 'e', 0, OptionArg.STRING, ref equation_string, N_("Start with given equation"), "equation"},
28        { "version", 'v', 0, OptionArg.NONE, null, N_("Show release version"), null },
29        { null }
30    };
31
32    private const ActionEntry[] app_entries =
33    {
34        { "new-window", new_window_cb, null, null, null },
35        { "preferences", show_preferences_cb, null, null, null },
36        { "shortcuts", keyboard_shortcuts_cb, null, null, null },
37        { "help", help_cb, null, null, null },
38        { "about", about_cb, null, null, null },
39        { "quit", quit_cb, null, null, null },
40    };
41
42    public Calculator ()
43    {
44        Object (flags : ApplicationFlags.NON_UNIQUE, application_id : "org.gnome.Calculator");
45
46        add_main_option_entries (option_entries);
47    }
48
49    private MathWindow create_new_window (Settings settings)
50    {
51        var accuracy = settings.get_int ("accuracy");
52        var word_size = settings.get_int ("word-size");
53        var number_base = settings.get_int ("base");
54        var show_tsep = settings.get_boolean ("show-thousands");
55        var show_zeroes = settings.get_boolean ("show-zeroes");
56        var number_format = (DisplayFormat) settings.get_enum ("number-format");
57        var angle_units = (AngleUnit) settings.get_enum ("angle-units");
58        var button_mode = (ButtonMode) settings.get_enum ("button-mode");
59        var source_currency = settings.get_string ("source-currency");
60        var target_currency = settings.get_string ("target-currency");
61        var source_units = settings.get_string ("source-units");
62        var target_units = settings.get_string ("target-units");
63        var precision = settings.get_int ("precision");
64
65        var equation = new MathEquation ();
66        equation.accuracy = accuracy;
67        equation.word_size = word_size;
68        equation.show_thousands_separators = show_tsep;
69        equation.show_trailing_zeroes = show_zeroes;
70        equation.number_format = number_format;
71        equation.angle_units = angle_units;
72        equation.source_currency = source_currency;
73        equation.target_currency = target_currency;
74        equation.source_units = source_units;
75        equation.target_units = target_units;
76        Number.precision = precision;
77
78        add_action_entries (app_entries, this);
79
80        var current_window = new MathWindow (this, equation);
81        current_window.set_title (_("Calculator"));
82        // when closing the last window save its position to the settings
83        current_window.delete_event.connect((sender, event) => {
84            if (n_math_windows == 1) {
85                save_window_position ((sender as MathWindow));
86            }
87            n_math_windows -= 1;
88            return false;
89        });
90        n_math_windows += 1;
91
92        var buttons = current_window.buttons;
93        buttons.programming_base = number_base;
94        buttons.mode = button_mode; // FIXME: We load the basic buttons even if we immediately switch to the next type
95
96        set_accels_for_action ("win.mode::basic", {"<control><alt>B"});
97        set_accels_for_action ("win.mode::advanced", {"<control><alt>A"});
98        set_accels_for_action ("win.mode::financial", {"<control><alt>F"});
99        set_accels_for_action ("win.mode::programming", {"<control><alt>P"});
100        set_accels_for_action ("win.mode::keyboard", {"<control><alt>K", "<control><alt>T"});
101        set_accels_for_action ("win.copy", {"<control>C"});
102        set_accels_for_action ("win.paste", {"<control>V"});
103        set_accels_for_action ("win.undo", {"<control>Z"});
104        set_accels_for_action ("win.close", {"<control>W"});
105        set_accels_for_action ("win.redo", {"<control><shift>Z"});
106        set_accels_for_action ("win.clear", {"<Primary>Escape"});
107
108        set_accels_for_action ("app.quit", {"<control>Q"});
109        set_accels_for_action ("app.new-window", {"<control>N"});
110        set_accels_for_action ("app.help", {"F1"});
111        set_accels_for_action ("app.shortcuts", {"<control>question"});
112        return current_window;
113    }
114
115    protected override void startup ()
116    {
117        base.startup ();
118
119        Hdy.init ();
120
121        settings = new Settings ("org.gnome.calculator");
122        settings.delay ();
123        last_opened_window = create_new_window (settings);
124        // restore the first window position from the settings
125        load_window_position (last_opened_window);
126        CurrencyManager.get_default ().refresh_interval = settings.get_int ("refresh-interval");
127        CurrencyManager.get_default ().refresh_async ();
128
129        settings.changed["refresh-interval"].connect(() => {
130            CurrencyManager.get_default ().refresh_interval = settings.get_int ("refresh-interval");
131            CurrencyManager.get_default ().refresh_async ();
132        });
133    }
134
135    private MathWindow get_active_math_window ()
136    {
137        return (MathWindow) get_active_window ();
138    }
139
140    protected override void activate ()
141    {
142        base.activate ();
143
144        last_opened_window.present ();
145        if (equation_string != "" && equation_string != null)
146        {
147            var equations = (equation_string.compress ()).split ("\n",0);
148            for (var i = 0; i < equations.length; i++)
149            {
150                if ((equations [i].strip ()).length > 0)
151                    last_opened_window.equation.set (equations [i]);
152                else
153                    last_opened_window.equation.solve ();
154            }
155        }
156        if (mode_string != "" && mode_string != null)
157        {
158            var mode = ButtonMode.BASIC;
159
160            switch (mode_string)
161            {
162            case "basic":
163                mode = ButtonMode.BASIC;
164                break;
165            case "advanced":
166                mode = ButtonMode.ADVANCED;
167                break;
168            case "financial":
169                mode = ButtonMode.FINANCIAL;
170                break;
171            case "programming":
172                mode = ButtonMode.PROGRAMMING;
173                break;
174            case "keyboard":
175                mode = ButtonMode.KEYBOARD;
176                break;
177            }
178            last_opened_window.buttons.mode = mode;
179        }
180    }
181
182    protected override void shutdown ()
183    {
184        base.shutdown ();
185
186        var window = last_opened_window;
187        var equation = window.equation;
188        var buttons = window.buttons;
189
190        settings.set_enum ("button-mode", buttons.mode);
191        settings.set_int ("accuracy", equation.accuracy);
192        settings.set_int ("word-size", equation.word_size);
193        settings.set_boolean ("show-thousands", equation.show_thousands_separators);
194        settings.set_boolean ("show-zeroes", equation.show_trailing_zeroes);
195        settings.set_enum ("number-format", equation.number_format);
196        settings.set_enum ("angle-units", equation.angle_units);
197        settings.set_string ("source-currency", equation.source_currency);
198        settings.set_string ("target-currency", equation.target_currency);
199        settings.set_string ("source-units", equation.source_units);
200        settings.set_string ("target-units", equation.target_units);
201        settings.set_int ("base", buttons.programming_base);
202        settings.apply ();
203    }
204
205    protected override int handle_local_options (GLib.VariantDict options)
206    {
207        if (options.contains ("version"))
208        {
209            /* NOTE: Is not translated so can be easily parsed */
210            stderr.printf ("%1$s %2$s\n", program_name, VERSION);
211            return Posix.EXIT_SUCCESS;
212        }
213
214        if (options.contains ("solve"))
215        {
216            var solve_equation = (string) options.lookup_value ("solve", VariantType.STRING);
217            var tsep_string = Posix.nl_langinfo (Posix.NLItem.THOUSEP);
218            if (tsep_string == null || tsep_string == "")
219                tsep_string = " ";
220
221            var decimal = Posix.nl_langinfo (Posix.NLItem.RADIXCHAR);
222            if (decimal == null)
223                decimal = "";
224
225            settings = new Settings ("org.gnome.calculator");
226            var angle_units = (AngleUnit) settings.get_enum ("angle-units");
227            var e = new ConvertEquation (solve_equation.replace (tsep_string, "").replace (decimal, "."));
228            e.base = 10;
229            e.wordlen = 32;
230            e.angle_units = angle_units;
231
232            ErrorCode error;
233            string? error_token = null;
234            uint representation_base;
235            var result = e.parse (out representation_base, out error, out error_token);
236
237            // if unknown conversion, try force reloading conversion rates and retry conversion
238            if (error == ErrorCode.UNKNOWN_CONVERSION) {
239                CurrencyManager.get_default ().refresh_interval = settings.get_int ("refresh-interval");
240                CurrencyManager.get_default ().refresh_sync ();
241                result = e.parse (out representation_base, out error, out error_token);
242            }
243            if (result != null)
244            {
245                var serializer = new Serializer (DisplayFormat.AUTOMATIC, 10, 9);
246                serializer.set_representation_base (representation_base);
247                var eq_result = serializer.to_string (result);
248                if (serializer.error != null) {
249                    stderr.printf (serializer.error);
250                    return Posix.EXIT_FAILURE;
251                }
252
253                stdout.printf ("%s\n", eq_result);
254                return Posix.EXIT_SUCCESS;
255            }
256            else if (error == ErrorCode.MP)
257            {
258                stderr.printf ("Error: %s\n", (Number.error != null) ? Number.error : error_token);
259                return Posix.EXIT_FAILURE;
260            }
261            else
262            {
263                stderr.printf ("Error: %s\n", mp_error_code_to_string (error));
264                return Posix.EXIT_FAILURE;
265            }
266        }
267
268        return -1;
269    }
270
271    private void show_preferences_cb ()
272    {
273        if (preferences_dialog == null)
274        {
275            preferences_dialog = new MathPreferencesDialog (get_active_math_window ().equation);
276            preferences_dialog.set_transient_for (get_active_window ());
277        }
278        preferences_dialog.present ();
279    }
280
281    private void keyboard_shortcuts_cb ()
282    {
283        if (shortcuts_window == null)
284        {
285            var builder = new Gtk.Builder ();
286            try
287            {
288                builder.add_from_resource ("/org/gnome/calculator/math-shortcuts.ui");
289            }
290            catch (Error e)
291            {
292                error ("Error loading shortcuts window UI: %s", e.message);
293            }
294
295            shortcuts_window = builder.get_object ("shortcuts-calculator") as Gtk.ShortcutsWindow;
296            shortcuts_window.destroy.connect ( (event) => { shortcuts_window = null; });
297        }
298
299        if (get_active_window () != shortcuts_window.get_transient_for ())
300            shortcuts_window.set_transient_for (get_active_window ());
301        shortcuts_window.show_all ();
302        shortcuts_window.present ();
303    }
304
305    private void help_cb ()
306    {
307        try
308        {
309            Gtk.show_uri_on_window (get_active_window (), "help:gnome-calculator", Gtk.get_current_event_time ());
310        }
311        catch (Error e)
312        {
313            /* Translators: Error message displayed when unable to launch help browser */
314            var message = _("Unable to open help file");
315
316            var d = new Gtk.MessageDialog (get_active_window (),
317                                           Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
318                                           Gtk.MessageType.ERROR,
319                                           Gtk.ButtonsType.CLOSE,
320                                           "%s", message);
321            d.format_secondary_text ("%s", e.message);
322            d.run ();
323            d.destroy ();
324        }
325    }
326
327    private void about_cb ()
328    {
329        string[] authors =
330        {
331            "Robert Roth <robert.roth.off@gmail.com>",
332            "Robert Ancell",
333            "Klaus Niederkrüger",
334            "Robin Sonefors",
335            "Rich Burridge",
336            null
337        };
338        string[] documenters =
339        {
340            "Sun Microsystems",
341            null
342        };
343
344        /* The translator credits. Please translate this with your name (s). */
345        var translator_credits = _("translator-credits");
346
347        Gtk.show_about_dialog (get_active_window (),
348                               "program-name",
349                               /* Program name in the about dialog */
350                               _("Calculator"),
351                               "title", _("About Calculator"),
352                               "version", VERSION,
353                               "website", "https://wiki.gnome.org/Apps/Calculator",
354                               "copyright",
355                               "\xc2\xa9 1986–2016 The Calculator authors",
356                               /* We link to MPFR and MPC which are  LGPLv3+, so Calculator cannot be conveyed as GPLv2+ */
357                               "license-type", Gtk.License.GPL_3_0,
358                               "comments",
359                               /* Short description in the about dialog */
360                               _("Calculator with financial and scientific modes."),
361                               "authors", authors,
362                               "documenters", documenters,
363                               "translator_credits", translator_credits,
364                               "logo-icon-name", "org.gnome.Calculator");
365    }
366
367    private void quit_cb ()
368    {
369        save_window_position (get_active_math_window ());
370
371        if (get_windows ().length () > 1)
372        {
373            var dialog = new Gtk.MessageDialog.with_markup (get_active_math_window (), Gtk.DialogFlags.MODAL,
374                                                            Gtk.MessageType.QUESTION, Gtk.ButtonsType.CANCEL,
375                                                            _("Are you sure you want to close all open windows?"));
376            dialog.add_buttons (_("Close _All"), Gtk.ResponseType.CLOSE);
377
378            int result = dialog.run ();
379            if (result == Gtk.ResponseType.CLOSE)
380                this.quit ();
381            dialog.destroy ();
382        } else {
383            this.quit ();
384        }
385    }
386
387    private void new_window_cb ()
388    {
389        var window = create_new_window (settings);
390        window.show ();
391    }
392
393    /**
394     * Load `window-position` from the settings and move the window to that
395     * position
396     */
397    private void load_window_position (MathWindow window) {
398        int32 x, y;
399        settings.get("window-position", "(ii)", out x, out y);
400        // (-1, -1) is the default value
401        if (x != -1 && y != -1) {
402            window.move (x, y);
403        }
404    }
405
406    /**
407     * Save window position to the settings
408     */
409    private void save_window_position (MathWindow window) {
410        int32 x, y;
411        window.get_position (out x, out y);
412        settings.set_value("window-position", new Variant("(ii)", x, y));
413    }
414
415    public static int main (string[] args)
416    {
417        Intl.setlocale (LocaleCategory.ALL, "");
418        Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR);
419        Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
420        Intl.textdomain (GETTEXT_PACKAGE);
421
422        /* Seed random number generator. */
423        var now = new DateTime.now_utc ();
424        Random.set_seed (now.get_microsecond ());
425
426        program_name = Path.get_basename (args [0]);
427
428        Gtk.Window.set_default_icon_name ("org.gnome.Calculator-symbolic");
429
430        var app = new Calculator ();
431
432        return app.run (args);
433    }
434}
435