1/*
2 * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (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
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
17 *
18 * Authored by Magnus Kulke <mkulke@gmail.com>
19 *
20 */
21
22namespace Synapse
23{
24  public class CalculatorPlugin : Object, Activatable, ItemProvider
25  {
26    public bool enabled { get; set; default = true; }
27
28    public void activate ()
29    {
30
31    }
32
33    public void deactivate ()
34    {
35
36    }
37
38    private class Result : TextMatch
39    {
40      public int default_relevancy { get; set; default = 0; }
41
42      public Result (double result, string match_string)
43      {
44        Object (title: "%g".printf (result),
45                description: "%s = %g".printf (match_string, result),
46                has_thumbnail: false, icon_name: "accessories-calculator");
47      }
48
49      public override string get_text ()
50      {
51        return title;
52      }
53    }
54
55    static void register_plugin ()
56    {
57      PluginRegistry.get_default ().register_plugin (
58        typeof (CalculatorPlugin),
59        _("Calculator"),
60        _("Calculate basic expressions."),
61        "accessories-calculator",
62        register_plugin,
63        Environment.find_program_in_path ("bc") != null,
64        _("bc is not installed")
65      );
66    }
67
68    static construct
69    {
70      register_plugin ();
71    }
72
73    private Regex regex;
74
75    construct
76    {
77      /* The regex describes a string which *resembles* a mathematical expression. It does not
78         check for pairs of parantheses to be used correctly and only whitespace-stripped strings
79         will match. Basically it matches strings of the form:
80         "paratheses_open* number (operator paratheses_open* number paratheses_close*)+"
81      */
82      try
83      {
84        regex = new Regex ("^\\(*(-?\\d+([.,]\\d+)?)([*/+-^]\\(*(-?\\d+([.,]\\d+)?)\\)*)+$",
85                         RegexCompileFlags.OPTIMIZE);
86      } catch (Error e) {
87        critical ("Error creating regexp.");
88      }
89    }
90
91    public bool handles_query (Query query)
92    {
93      return (QueryFlags.ACTIONS in query.query_type);
94    }
95
96    public async ResultSet? search (Query query) throws SearchError
97    {
98      string input = query.query_string.replace (" ", "").replace (",", ".");
99      bool matched = regex.match (input);
100      if (!matched && input.length > 1)
101      {
102        input = input[0 : input.length - 1];
103        matched = regex.match (input);
104      }
105      if (matched)
106      {
107        Pid pid;
108        int read_fd, write_fd;
109        string[] argv = {"bc", "-l"};
110        string? solution = null;
111
112        try
113        {
114          Process.spawn_async_with_pipes (null, argv, null,
115                                          SpawnFlags.SEARCH_PATH,
116                                          null, out pid, out write_fd, out read_fd);
117          UnixInputStream read_stream = new UnixInputStream (read_fd, true);
118          DataInputStream bc_output = new DataInputStream (read_stream);
119
120          UnixOutputStream write_stream = new UnixOutputStream (write_fd, true);
121          DataOutputStream bc_input = new DataOutputStream (write_stream);
122
123          bc_input.put_string (input + "\n", query.cancellable);
124          yield bc_input.close_async (Priority.DEFAULT, query.cancellable);
125          solution = yield bc_output.read_line_async (Priority.DEFAULT_IDLE, query.cancellable);
126
127          if (solution != null)
128          {
129            double d = double.parse (solution);
130            Result result = new Result (d, query.query_string);
131            ResultSet results = new ResultSet ();
132            results.add (result, MatchScore.AVERAGE);
133            query.check_cancellable ();
134            return results;
135          }
136        }
137        catch (Error err)
138        {
139          if (!query.is_cancelled ()) warning ("%s", err.message);
140        }
141      }
142
143      query.check_cancellable ();
144      return null;
145    }
146  }
147}
148