Merge lp:~toineo/unity-lens-applications/calculator into lp:unity-lens-applications
- calculator
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~toineo/unity-lens-applications/calculator |
Merge into: | lp:unity-lens-applications |
Diff against target: |
876 lines (+740/-54) 4 files modified
src/Makefile.am (+1/-0) src/equationparser.vala (+416/-0) src/runner.vala (+170/-53) src/utils.vala (+153/-1) |
To merge this branch: | bzr merge lp:~toineo/unity-lens-applications/calculator |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
toineo (community) | Abstain | ||
Didier Roche-Tolomelli | Needs Fixing | ||
Review via email: mp+70264@code.launchpad.net |
This proposal has been superseded by a proposal from 2011-09-09.
Commit message
Description of the change
This mainly adds a calculator to the <alt-f2> mode.
The refactored runner should also permit easier integration of other modes, in the future.
To try it : <alt-f2> + "= yourexpression", e.g "= 2 * tan(7) / 5"
It implements bug #778036.
I would like to thank Didrocks for his very useful help in making this little contribution, and for the time he spent answering my questions.
Didier Roche-Tolomelli (didrocks) wrote : | # |
opss, wrong window, sorry, was looking at another merge, will look at the one later ;)
Didier Roche-Tolomelli (didrocks) : | # |
Didier Roche-Tolomelli (didrocks) wrote : | # |
Hey toineo,
Thanks for this awesome work!
so some comments on the proposed branch:
+ if ((result = process_easter (ref search_string, ref model)) != 0)
I would just make process_easter returns true or false and don't care about the result (we don't really need to differentiate them)
----------------
549 + string uri;
550 + Icon icon;
551 + uri = "unity-calc";
552 + string result_string = _(format_
-> can you pease fix the ident?
575 + string uri;
576 + Icon icon;
577 + uri = "unity-calc";
578 + string result_string = _(format_
-> same remark
Btw, you have this code twice, can you try a while loop with an arg being true as long as the result is invalid and we can still tweak the search by adding ')' and such?
-------
63 + private Gee.HashMap<
64 + private Gee.HashMap<
65 + private Gee.HashMap<
66 + private Gee.HashMap<
As we discussed, I would prefer an double HashMap with 2,3,4,5 has a primary index to select the right subhashmap reference
-------
256 + /* FIXME : at this time, something like tan7 is evaluated
257 + * as tan(7). Do we accept this behavior ? */
-> it's fine for now ;)
Awesome work! can you please look at those remarks and see how to fix them? The rest looks good :-)
Thanks again for working on that!
toineo (toineo) wrote : | # |
Didrocks, fix commited for this. Thank you for reviewing, and for your help !
toineo (toineo) wrote : | # |
History should now show with an empty search, as in the trunk version.
toineo (toineo) wrote : | # |
The merge request is frozen for now. I have fixed the bug of bad double printing (e.g something like 2.1 that was printed as 2.0999999999998) -- and I should push those changes --, but we still have difficulties to know if the computation was exact or not (and that will be clearly visible to the user, through the equal sign (= or ≈, depending on the case)).
I see 3 way to fix it :
- Add some code to the equation parser that detects approximations. This should work for many simple (or not so simple) cases, but would probably still fail on more difficult ones.
- Use an external binding for a bignum (including bigfloat) library. With a good library, we should make almost no error telling if the computation was exact or not. Unfortunately, I found no such binding for vala...
- Write an implementation of bignums directly in the parser. But it would take some time, in particular for some operations/
If anyone had a solution or something that could help, it would be greatly appreciated !
(Sorry for my *bad* English...)
Mikkel Kamstrup Erlandsen (kamstrup) wrote : | # |
The simpler solution the better I'd say. Pulling in some numeric lib
just to support this seems way out of proportion; writing custom
arbitrary precision math routines or advanced parsers is way out of
scope for something that's definitely never intended as a full fledged
calculator.
If we can just always get a sensible result I think it is good enough
- no need to differentiate = or ≈. If people care about high precision
math I don't think the dash is the place to look for it. - or it would
have to go in a dedicated math lens at the very least (woohoo, i see
symbolic equation solvers coming in ;-))
toineo (toineo) wrote : | # |
Thank you for your answer Mikkel !
Well, last time I discussed with Didier, he told me he really prefers a calculator with an accurate differentiation of = and ≈, and that an user who asks to compute something like 10^18 + 2 (but less obvious, of course) will be unhappy with a result of 1×10¹⁸ and nothing indicating it is an approximation. I agree with it -- even if, you are completely right, it is not intended to be a precision calculator. It is just that it is a pity not to be able to warn the user ; but I agree, the means have to be commensurate with the goals.
For a version with no equal/approximate sign (or with the same sign whatever the computation), the last revision of this branch should be almost ready for review (it only needs a fix for the bug of broken function calls), I have not currently found other bugs.
I find the idea of a math lens great ! It would be a killer feature ! It could completely go along with the alt-f2 calculator (which is only for some quick results, and could possibly have a link to the math lens). I want it :D
(Same thing, sorry for my english...)
toineo (toineo) wrote : | # |
Now, the branch should be up-to-date with trunk.
(I switch to abstain because I cannot switch to "needs reviews")
Unmerged revisions
- 219. By toineo
-
Remerged against njpatel's and didrocks' work
- 218. By toineo
-
Fix the bug of broken function calls.
Fix a bug due to the "useless + deleting" (now, (+-+-7) is correctly evaluated).
Add log10 to the function set. - 217. By toineo
-
Fixing the approximations due to the double type.
Removing the display of the search string, before the result. - 216. By toineo
-
Improving results display.
- 215. By toineo
-
Fixing regression of history not displayed on empty search.
Removing useless comments and fixing indentation. - 214. By toineo
-
Implementing double hashmap for size-based function processing.
Fixing minor issues (bool process_easter(), indent problem, shameful process_computation( ) structure). - 213. By toineo
-
Refactored runner with mode support
Adding equation parser and corresponding calculator mode in runner
Preview Diff
1 | === modified file 'src/Makefile.am' |
2 | --- src/Makefile.am 2011-08-04 07:27:33 +0000 |
3 | +++ src/Makefile.am 2011-08-12 07:58:24 +0000 |
4 | @@ -52,6 +52,7 @@ |
5 | runner.vala \ |
6 | schemas.vala \ |
7 | utils.vala \ |
8 | + equationparser.vala \ |
9 | $(NULL) |
10 | |
11 | unity-package-search.o : $(srcdir)/unity-package-search.cc $(srcdir)/unity-package-search.h |
12 | |
13 | === added file 'src/equationparser.vala' |
14 | --- src/equationparser.vala 1970-01-01 00:00:00 +0000 |
15 | +++ src/equationparser.vala 2011-08-12 07:58:24 +0000 |
16 | @@ -0,0 +1,416 @@ |
17 | +/* |
18 | + * Copyright (C) 2011 Canonical Ltd |
19 | + * |
20 | + * This program is free software: you can redistribute it and/or modify |
21 | + * it under the terms of the GNU General Public License version 3 as |
22 | + * published by the Free Software Foundation. |
23 | + * |
24 | + * This program is distributed in the hope that it will be useful, |
25 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
26 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
27 | + * GNU General Public License for more details. |
28 | + * |
29 | + * You should have received a copy of the GNU General Public License |
30 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
31 | + * |
32 | + * Authored by Antoine Voizard |
33 | + * |
34 | + */ |
35 | + |
36 | + |
37 | +using Gee; |
38 | + |
39 | +namespace Unity.ApplicationsLens |
40 | +{ |
41 | + private delegate double Delegate1(double a); |
42 | + private delegate double Delegate2(double a, double b); // Math operators |
43 | + |
44 | + private class DelegateWrapper1 |
45 | + { |
46 | + public Delegate1 d; |
47 | + public DelegateWrapper1(Delegate1 d) |
48 | + { |
49 | + this.d = d; |
50 | + } |
51 | + } |
52 | + private class DelegateWrapper2 |
53 | + { |
54 | + public Delegate2 d; |
55 | + public DelegateWrapper2(Delegate2 d) |
56 | + { |
57 | + this.d = d; |
58 | + } |
59 | + } |
60 | + |
61 | + private class EquationParser |
62 | + { |
63 | + private Gee.HashMap<int,Gee.HashMap<string,DelegateWrapper1>> Functions; |
64 | + private const int MAXFCTSIZE = 5; |
65 | + private const int MINFCTSIZE = 2; |
66 | + |
67 | + private Gee.HashMap<string,DelegateWrapper2> Operators; |
68 | + private ArrayList<string> [] PrioritizedOperators;// = {{"^"}, {"*", "/"}, {"+", "-"}}; |
69 | + |
70 | + private char [] signs = {'+', '-'}; |
71 | + |
72 | + public int MaxPrec {get; private set;} |
73 | + |
74 | + public EquationParser () |
75 | + { |
76 | + Functions = new Gee.HashMap<int,Gee.HashMap<string,DelegateWrapper1>> (); |
77 | + for (int i = MINFCTSIZE; i <= MAXFCTSIZE; i++) |
78 | + Functions[i] = new Gee.HashMap<string,DelegateWrapper1> (); |
79 | + |
80 | + Operators = new Gee.HashMap<string,DelegateWrapper2> (); |
81 | + |
82 | + PrioritizedOperators = {new ArrayList<string> (), |
83 | + new ArrayList<string> (), |
84 | + new ArrayList<string> ()}; |
85 | + |
86 | + Functions[2]["ln"] = new DelegateWrapper1 (Math.log); |
87 | + Functions[2]["th"] = new DelegateWrapper1 (Math.tanh); |
88 | + Functions[2]["sh"] = new DelegateWrapper1 (Math.sinh); |
89 | + Functions[2]["ch"] = new DelegateWrapper1 (Math.cosh); |
90 | + Functions[2]["j0"] = new DelegateWrapper1 (Math.j0); // Bessel function, just in case |
91 | + Functions[2]["j1"] = new DelegateWrapper1 (Math.j1); |
92 | + Functions[2]["y0"] = new DelegateWrapper1 (Math.y0); |
93 | + Functions[2]["y1"] = new DelegateWrapper1 (Math.y1); |
94 | + |
95 | + Functions[3]["sin"] = new DelegateWrapper1 (Math.sin); |
96 | + Functions[3]["cos"] = new DelegateWrapper1 (Math.cos); |
97 | + Functions[3]["tan"] = new DelegateWrapper1 (Math.tan); |
98 | + Functions[3]["exp"] = new DelegateWrapper1 (Math.exp); |
99 | + Functions[3]["abs"] = new DelegateWrapper1 (Math.fabs); |
100 | + Functions[3]["log"] = new DelegateWrapper1 (Math.log); |
101 | + Functions[3]["erf"] = new DelegateWrapper1 (Math.erf); |
102 | + |
103 | + Functions[4]["asin"] = new DelegateWrapper1 (Math.asin); |
104 | + Functions[4]["acos"] = new DelegateWrapper1 (Math.acos); |
105 | + Functions[4]["atan"] = new DelegateWrapper1 (Math.atan); |
106 | + Functions[4]["sinh"] = new DelegateWrapper1 (Math.sinh); |
107 | + Functions[4]["tanh"] = new DelegateWrapper1 (Math.tanh); |
108 | + Functions[4]["cosh"] = new DelegateWrapper1 (Math.cosh); |
109 | + Functions[4]["ceil"] = new DelegateWrapper1 (Math.ceil); |
110 | + Functions[4]["log2"] = new DelegateWrapper1 ((a) => {return Math.log (a) / Math.LN2;}); |
111 | + |
112 | + Functions[5]["trunc"] = new DelegateWrapper1 (Math.trunc); |
113 | + Functions[5]["round"] = new DelegateWrapper1 (Math.round); |
114 | + Functions[5]["floor"] = new DelegateWrapper1 (Math.floor); |
115 | + Functions[5]["asinh"] = new DelegateWrapper1 (Math.asinh); |
116 | + Functions[5]["acosh"] = new DelegateWrapper1 (Math.acosh); |
117 | + Functions[5]["atanh"] = new DelegateWrapper1 (Math.atanh); |
118 | + Functions[5]["log10"] = new DelegateWrapper1 (Math.log10); |
119 | + |
120 | + // TODO : cotan, etc ? |
121 | + |
122 | + Operators["+"] = new DelegateWrapper2 ((a, b) => {return a + b;}); |
123 | + Operators["-"] = new DelegateWrapper2 ((a, b) => {return a - b;}); |
124 | + Operators["*"] = new DelegateWrapper2 ((a, b) => {return a * b;}); |
125 | + Operators["/"] = new DelegateWrapper2 ((a, b) => {return a / b;}); |
126 | + Operators["^"] = new DelegateWrapper2 (Math.pow); |
127 | + |
128 | + /* Cheap way to force priority between operators, and to process |
129 | + * equal-priority operators together */ |
130 | + PrioritizedOperators[0].add ("^"); |
131 | + PrioritizedOperators[1].add ("*"); |
132 | + PrioritizedOperators[1].add ("/"); |
133 | + PrioritizedOperators[2].add ("+"); |
134 | + PrioritizedOperators[2].add ("-"); |
135 | + |
136 | + MaxPrec = int.parse (double.EPSILON.to_string().split("e")[1].replace("-", "")); |
137 | + |
138 | + } |
139 | + |
140 | + private bool find_previous_block (string eq, int position, out int result) |
141 | + { |
142 | + int current = position; |
143 | + int level = 0; |
144 | + |
145 | + if (position == 0) return false; |
146 | + |
147 | + // Spot eventual parentheses that delimit current block |
148 | + while (current != 0) |
149 | + { |
150 | + current--; |
151 | + if (eq[current] == ')') |
152 | + level++; |
153 | + else if (eq[current] == '(') |
154 | + { |
155 | + level--; |
156 | + if (level == 0) break; |
157 | + else if (level == -1) |
158 | + { |
159 | + current++; |
160 | + level = 0; |
161 | + break; |
162 | + } |
163 | + } |
164 | + else if ((eq[current].to_string () in Operators.keys |
165 | + || eq[current] == ',') |
166 | + && level == 0) |
167 | + { |
168 | + current++; |
169 | + break; |
170 | + } |
171 | + } |
172 | + |
173 | + if (level != 0) return false; // Parentheses don't match |
174 | + |
175 | + // FIXME : if we write, for example, tanexp(x), it will compute tan(exp(x)). Is it ok ? |
176 | + /* We got the block, now let's check if it is |
177 | + * headed by a function ; if yes, include that |
178 | + * function in the block */ |
179 | + for (int size = MAXFCTSIZE; size >= MINFCTSIZE; size--) |
180 | + if (current >= size && eq[current - size : current] in Functions[size].keys) |
181 | + current -= size; |
182 | + |
183 | + // Heading minus sign ? |
184 | + if (current > 0 && eq[current - 1] == '~') |
185 | + current--; |
186 | + |
187 | + result = current; |
188 | + return true; |
189 | + } |
190 | + |
191 | + private bool find_next_block (string eq, int position, out int result) |
192 | + { |
193 | + int current = position + 1; |
194 | + int level = 0; |
195 | + |
196 | + if (current >= eq.length) return false; |
197 | + |
198 | + while (current < eq.length) |
199 | + { |
200 | + if (eq[current] == '(') |
201 | + level++; |
202 | + else if (eq[current] == ')') |
203 | + { |
204 | + level--; |
205 | + if (level == 0) break; |
206 | + else if (level == -1) |
207 | + { |
208 | + current--; |
209 | + level = 0; |
210 | + break; |
211 | + } |
212 | + } |
213 | + else if ((eq[current].to_string () in Operators.keys |
214 | + || eq[current] == ',') |
215 | + && level == 0) |
216 | + { |
217 | + current--; |
218 | + break; |
219 | + } |
220 | + |
221 | + current++; |
222 | + if (current == eq.length) |
223 | + { |
224 | + current--; |
225 | + break; |
226 | + } |
227 | + } |
228 | + |
229 | + if (level != 0 || current == position) return false; |
230 | + |
231 | + result = current; |
232 | + return true; |
233 | + } |
234 | + |
235 | + private bool eval_block (string _block, out double result) |
236 | + { |
237 | + var block = _block; // FIXME : useless copy ? (ownership problem...) |
238 | + bool inverse_sign = false; |
239 | + |
240 | + /* Hack that prevents problems with useless parentheses, |
241 | + * and treats the sign inverter (~). Both cases need to |
242 | + * be treated together ; can be done with recursive call, btw */ |
243 | + while ((block.has_prefix ("(") && block.has_suffix (")")) |
244 | + ||block[0] == '~') |
245 | + if (block[0] == '~') |
246 | + { |
247 | + inverse_sign = !inverse_sign; |
248 | + block = block [1:block.length]; |
249 | + } |
250 | + else |
251 | + block = block[1 : -1]; |
252 | + |
253 | + // Determines if we got a function in the front of the block |
254 | + /* FIXME : at this time, something like tan7 is evaluated |
255 | + * as tan(7). Do we accept this behavior ? */ |
256 | + while (true) |
257 | + { |
258 | + bool fct_found = false; |
259 | + for (int size = MAXFCTSIZE; size >= MINFCTSIZE; size--) |
260 | + if (block.length > size && block[0 : size] in Functions[size].keys) |
261 | + { |
262 | + double sub_result; |
263 | + if (!eval_block (block[size : block.length], out sub_result)) |
264 | + return false; |
265 | + |
266 | + result = Functions[size][block[0 : size]].d (sub_result); |
267 | + |
268 | + fct_found = true; |
269 | + break; |
270 | + } |
271 | + if (fct_found) break; // We can't break the while loop inside the for one |
272 | + |
273 | + |
274 | + // Now search for operators |
275 | + if (block[0].to_string() in Operators.keys) |
276 | + { |
277 | + int position1, position2; // Position of term 1 and 2 |
278 | + double sub_result1 = 0, sub_result2 = 0; |
279 | + |
280 | + // Get first term |
281 | + if (!(find_next_block (block, 1, out position1) |
282 | + && eval_block (block[2 : position1 + 1], out sub_result1))) |
283 | + return false; |
284 | + |
285 | + // Second term |
286 | + if (!(find_next_block (block, position1 + 1, out position2) |
287 | + && eval_block (block[position1 + 2 : position2 + 1], out sub_result2))) |
288 | + return false; |
289 | + |
290 | + result = Operators[block[0].to_string()].d (sub_result1, sub_result2); |
291 | + break; |
292 | + } |
293 | + |
294 | + // Is the block a number ? |
295 | + if (block[0].isdigit ()) |
296 | + { |
297 | + result = double.parse (block); |
298 | + break; |
299 | + } |
300 | + |
301 | + // Constants (inelegant way...) |
302 | + if (block == "e") |
303 | + { |
304 | + result = Math.E; |
305 | + break; |
306 | + } |
307 | + if (block == "pi") |
308 | + { |
309 | + result = Math.PI; |
310 | + break; |
311 | + } |
312 | + |
313 | + return false; |
314 | + } |
315 | + |
316 | + if (inverse_sign) result = 0 - result; |
317 | + |
318 | + return true; |
319 | + } |
320 | + |
321 | + public bool compute (string equation, out double result) |
322 | + { |
323 | + var eq = equation.down ().replace (",", ".").replace("~", ""); |
324 | + bool no_error = true; |
325 | + |
326 | + // TODO : test equation's correctness |
327 | + if (eq.length == 0) |
328 | + return false; |
329 | + |
330 | + if (eq.has_prefix ("+") || eq.has_prefix ("-")) |
331 | + eq = "0" + eq; |
332 | + |
333 | + |
334 | + for (int i = 0, size; i + 1 < eq.length; i++) |
335 | + { |
336 | + // Merge consecutive plus and minus sign |
337 | + if (eq[i] in signs && eq[i+1] in signs) |
338 | + { |
339 | + if (eq[i] == eq[i+1]) |
340 | + eq = eq[0:i] + "+" + eq[i+2:eq.length]; |
341 | + else |
342 | + eq = eq[0:i] + "-" + eq[i+2:eq.length]; |
343 | + |
344 | + i--; |
345 | + continue; |
346 | + } |
347 | + |
348 | + // Protect function calls |
349 | + // FIXME : this is heavy, but I have currently no other idea |
350 | + // for this test... |
351 | + if (eq[i].isalpha()) |
352 | + for (size = MAXFCTSIZE; size >= MINFCTSIZE; size--) |
353 | + if (i <= eq.length - size && eq[i : i + size] in Functions[size].keys) |
354 | + { |
355 | + i += size; |
356 | + eq = eq[0 : i] + "#" + eq[i : eq.length]; |
357 | + break; |
358 | + } |
359 | + |
360 | + // Add implicit "*" operator |
361 | + if ((eq[i].isdigit () && eq[i + 1].isalpha ()) |
362 | + ||(eq[i].isdigit () && eq[i + 1] == '(') |
363 | + ||(eq[i].isalpha () && eq[i + 1] == '(') |
364 | + ||(eq[i] == ')' && eq[i + 1] == '(') |
365 | + ||(eq[i] == ')' && eq[i + 1].isalpha ())) |
366 | + eq = eq[0 : i + 1] + "*" + eq[i + 1 : eq.length]; |
367 | + |
368 | + // Delete unneeded plus sign |
369 | + if (i > 0 |
370 | + && (eq[i-1].to_string () in Operators.keys || eq[i-1] == '(') |
371 | + && eq[i] == '+') |
372 | + { |
373 | + eq = eq[0:i] + eq[i+1:eq.length]; |
374 | + i--; |
375 | + //continue; // Uncomment if new tests are made in the loop, after this one |
376 | + } |
377 | + |
378 | + } |
379 | + |
380 | + eq = eq.replace("#", ""); |
381 | + |
382 | + /* Replace minus sign by ~ (easier to treat) |
383 | + * This loop *has* to be separated from the previous, |
384 | + * elsewhere it will interfer with the +/- merging */ |
385 | + for (int i = 0; i + 1 < eq.length; i++) |
386 | + if ((eq[i].to_string () in Operators.keys || eq[i] == '(') |
387 | + && eq[i+1] == '-') |
388 | + eq = eq[0:i+1] + "~" + eq[i+2:eq.length]; |
389 | + |
390 | + |
391 | + /* Search for operators in equation |
392 | + * The while loop does the search itself, and |
393 | + * the for loop ensures respect of operators priority */ |
394 | + for (int op_level = 0, i; op_level < PrioritizedOperators.length && no_error; op_level++) |
395 | + { |
396 | + i = 0; |
397 | + while (i < eq.length) |
398 | + { |
399 | + if (eq[i].to_string () in PrioritizedOperators[op_level]) |
400 | + { |
401 | + int position1 = 0, position2 = 0; |
402 | + // Find operands' position |
403 | + if (!find_previous_block (eq, i, out position1) |
404 | + ||!find_next_block (eq, i, out position2)) |
405 | + { |
406 | + debug ("Calculator : Incorrect equation"); |
407 | + no_error = false; |
408 | + return false; |
409 | + } |
410 | + // Reorganize operator and operands into polish notation |
411 | + // FIXME : avoid multiple useless parentheses |
412 | + eq = eq[0 : position1] + "(" + eq[i].to_string () + "," + eq[position1 : i] |
413 | + + "," + eq[i + 1 : position2 + 1] + ")" + eq[position2 + 1 : eq.length]; |
414 | + i += 3; |
415 | + } |
416 | + else i++; |
417 | + } |
418 | + } |
419 | + |
420 | + // Computation |
421 | + if (!eval_block (eq, out result)) |
422 | + { |
423 | + debug ("Calculator : Unable to compute"); |
424 | + return false; |
425 | + } |
426 | + |
427 | + return true; |
428 | + } |
429 | + |
430 | + } // equationparser |
431 | + |
432 | +} |
433 | |
434 | === modified file 'src/runner.vala' |
435 | --- src/runner.vala 2011-08-11 16:18:36 +0000 |
436 | +++ src/runner.vala 2011-08-12 07:58:24 +0000 |
437 | @@ -61,6 +61,8 @@ |
438 | |
439 | private Settings gp_settings; |
440 | |
441 | + private EquationParser parser; |
442 | + |
443 | public Runner (Unity.ApplicationsLens.Daemon daemon) |
444 | { |
445 | /* First create scope */ |
446 | @@ -129,6 +131,8 @@ |
447 | history = new Gee.ArrayList<string> (); |
448 | load_history (); |
449 | |
450 | + parser = new EquationParser (); |
451 | + |
452 | this.daemon = daemon; |
453 | |
454 | } |
455 | @@ -152,28 +156,11 @@ |
456 | private async void update_search (LensSearch? search) |
457 | { |
458 | var model = scope.results_model; |
459 | - var executables_match = new Gee.ArrayList<string> (); |
460 | - var dirs_match = new Gee.ArrayList<string> (); |
461 | model.clear (); |
462 | |
463 | var search_string = search.search_string; |
464 | bool has_search = !Utils.search_is_invalid (search); |
465 | - |
466 | - string uri; |
467 | - Icon icon; |
468 | - string mimetype; |
469 | - string display_name; |
470 | - var category_id = RunnerCategory.HISTORY; |
471 | - |
472 | - foreach (var command in this.history) |
473 | - { |
474 | - display_name = get_icon_uri_and_mimetype (command, out icon, out uri, out mimetype); |
475 | - model.append (uri, icon.to_string (), |
476 | - category_id, mimetype, |
477 | - display_name, |
478 | - null); |
479 | - } |
480 | - |
481 | + |
482 | /* Prevent concurrent searches and concurrent updates of our models, |
483 | * by preventing any notify signals from propagating to us. |
484 | * Important: Remember to thaw the notifys with release_scopelock()! */ |
485 | @@ -181,41 +168,133 @@ |
486 | |
487 | if (!has_search) |
488 | { |
489 | + display_history (ref model); |
490 | release_scopelock (); |
491 | search.finished (); |
492 | return; |
493 | } |
494 | |
495 | Timer timer = new Timer (); |
496 | + double result; |
497 | |
498 | /* no easter egg in unity */ |
499 | - if (search_string == "free the fish") |
500 | - { |
501 | - uri = "no-easter-egg"; |
502 | - string commenteaster = _("There is no easter egg in Unity"); |
503 | - icon = new ThemedIcon ("gnome-panel-fish"); |
504 | - model.append (uri, icon.to_string (), |
505 | - 0, "no-mime", |
506 | - commenteaster, |
507 | - null); |
508 | - release_scopelock (); |
509 | - search.finished (); |
510 | - return; |
511 | - } |
512 | - else if (search_string == "gegls from outer space") |
513 | - { |
514 | - uri = "really-no-easter-egg"; |
515 | - string commentnoeaster = _("Still no easter egg in Unity"); |
516 | - icon = new ThemedIcon ("gnome-panel-fish"); |
517 | - model.append (uri, icon.to_string (), |
518 | - 0, "no-mime", |
519 | - commentnoeaster, |
520 | - null); |
521 | - release_scopelock (); |
522 | - search.finished (); |
523 | - return; |
524 | - |
525 | - } |
526 | + if (process_easter (ref search_string, ref model)) |
527 | + { |
528 | + timer.stop (); |
529 | + debug ("Easter egg found in %fms", timer.elapsed ()*1000); |
530 | + } |
531 | + |
532 | + /* Calculator mode (e.g "= 2 + 1") */ |
533 | + else if (process_computation (ref search_string, ref model, out result)) |
534 | + { |
535 | + timer.stop (); |
536 | + debug ("Equation processed in %fms", timer.elapsed ()*1000); |
537 | + } |
538 | + |
539 | + /* Command mode */ |
540 | + else |
541 | + { |
542 | + var executables_match = new Gee.ArrayList<string> (); |
543 | + var dirs_match = new Gee.ArrayList<string> (); |
544 | + |
545 | + process_command (ref search_string, ref model, ref dirs_match, ref executables_match); |
546 | + |
547 | + timer.stop (); |
548 | + debug ("Entry search listed %i dir matches and %i exec matches in %fms for search: %s", |
549 | + dirs_match.size, executables_match.size, timer.elapsed ()*1000, search_string); |
550 | + } |
551 | + |
552 | + release_scopelock (); |
553 | + search.finished (); |
554 | + } |
555 | + |
556 | + private bool process_computation (ref string search_string, ref Dee.SharedModel model, out double result) |
557 | + { |
558 | + if (search_string.has_prefix ("=")) |
559 | + { |
560 | + string uri = "unity-calc://calc"; |
561 | + Icon icon = new ThemedIcon ("accessories-calculator"); |
562 | + string result_string = ""; |
563 | + |
564 | + if (search_string.length > 1) |
565 | + { |
566 | + search_string = search_string[1:search_string.length].replace (" ", ""); |
567 | + |
568 | + // Try to complete expression by adding some closing parentheses |
569 | + int NbOpenPar = search_string.length - search_string.replace ("(", "").length; |
570 | + int NbClosePar = search_string.length - search_string.replace (")", "").length; |
571 | + |
572 | + while (NbOpenPar > NbClosePar) |
573 | + { |
574 | + search_string += ")"; |
575 | + NbClosePar++; |
576 | + } |
577 | + if (parser.compute (search_string, out result)) |
578 | + { |
579 | + debug ("Computation : equation = " + search_string |
580 | + + ", result = " + result.to_string()); |
581 | + |
582 | + result_string = _(format_result_output (search_string, result)); |
583 | + } |
584 | + // We are definitely unable to compute that expression |
585 | + else |
586 | + { |
587 | + result_string = _("Unable to compute"); |
588 | + debug ("Unable to calculate that expression : " + search_string); |
589 | + } |
590 | + } |
591 | + else // Empty string case |
592 | + { |
593 | + result_string = _("calc mode"); |
594 | + debug ("Empty expression"); |
595 | + } |
596 | + |
597 | + model.append (uri, icon.to_string (), |
598 | + 0, "no-mime", |
599 | + result_string, |
600 | + null); |
601 | + |
602 | + return true; |
603 | + } |
604 | + |
605 | + else return false; |
606 | + } |
607 | + |
608 | + private string format_result_output (string _search_string, double result) |
609 | + { |
610 | + //string search_string; |
611 | + string equal_sign = " = "; // Don't forget to put spaces around it ! |
612 | + |
613 | + // Processing search_string (too long string will be shorted) |
614 | + /* FIXME : Choose if we want to display, or not, the search string |
615 | + * It is currently deactivated, to give a try * |
616 | + if (_search_string.length > 16) |
617 | + search_string = _search_string[0:5] + " … " + |
618 | + _search_string[_search_string.length-5:_search_string.length]; |
619 | + else search_string = _search_string; */ |
620 | + |
621 | + // Processing result |
622 | + if (result == double.INFINITY) return /*search_string + " = */ "+∞"; |
623 | + else if (result == -double.INFINITY) return /*search_string + " = */ "-∞"; |
624 | + else if (result.is_nan ()) return "Unable to compute"; |
625 | + else |
626 | + { |
627 | + bool approx; |
628 | + string formated_result = Utils.format_number (result, parser.MaxPrec, 21, out approx); |
629 | + if (approx) equal_sign = " ≈ "; |
630 | + return /*search_string + equal_sign +*/ formated_result; |
631 | + } |
632 | + } |
633 | + |
634 | + private void process_command (ref string search_string, ref Dee.SharedModel model, ref Gee.ArrayList<string> dirs_match, ref Gee.ArrayList<string> executables_match) |
635 | + { |
636 | + string uri; |
637 | + Icon icon; |
638 | + string mimetype; |
639 | + string display_name; |
640 | + var category_id = RunnerCategory.HISTORY; |
641 | + |
642 | + display_history (ref model); |
643 | |
644 | /* manual seek with directory and executables result */ |
645 | if (search_string.has_prefix ("/") || search_string.has_prefix ("~")) |
646 | @@ -319,14 +398,52 @@ |
647 | display_name, |
648 | null); |
649 | } |
650 | - |
651 | - timer.stop (); |
652 | - debug ("Entry search listed %i dir matches and %i exec matches in %fms for search: %s", |
653 | - dirs_match.size, executables_match.size, timer.elapsed ()*1000, search_string); |
654 | - |
655 | - |
656 | - release_scopelock (); |
657 | - search.finished (); |
658 | + } |
659 | + |
660 | + private bool process_easter (ref string search_string, ref Dee.SharedModel model) |
661 | + { |
662 | + /* no easter egg in unity */ |
663 | + if (search_string == "free the fish") |
664 | + { |
665 | + string uri = "no-easter-egg"; |
666 | + string commenteaster = _("There is no easter egg in Unity"); |
667 | + Icon icon = new ThemedIcon ("gnome-panel-fish"); |
668 | + model.append (uri, icon.to_string (), |
669 | + 0, "no-mime", |
670 | + commenteaster, |
671 | + null); |
672 | + } |
673 | + else if (search_string == "gegls from outer space") |
674 | + { |
675 | + string uri = "really-no-easter-egg"; |
676 | + string commentnoeaster = _("Still no easter egg in Unity"); |
677 | + Icon icon = new ThemedIcon ("gnome-panel-fish"); |
678 | + model.append (uri, icon.to_string (), |
679 | + 0, "no-mime", |
680 | + commentnoeaster, |
681 | + null); |
682 | + } |
683 | + else return false; |
684 | + |
685 | + return true; |
686 | + } |
687 | + |
688 | + private void display_history (ref Dee.SharedModel model) |
689 | + { |
690 | + string uri; |
691 | + Icon icon; |
692 | + string mimetype; |
693 | + string display_name; |
694 | + var category_id = RunnerCategory.HISTORY; |
695 | + |
696 | + foreach (var command in this.history) |
697 | + { |
698 | + display_name = get_icon_uri_and_mimetype (command, out icon, out uri, out mimetype); |
699 | + model.append (uri, icon.to_string (), |
700 | + category_id, mimetype, |
701 | + display_name, |
702 | + null); |
703 | + } |
704 | } |
705 | |
706 | private void release_scopelock () |
707 | |
708 | === modified file 'src/utils.vala' |
709 | --- src/utils.vala 2011-07-18 22:29:11 +0000 |
710 | +++ src/utils.vala 2011-08-12 07:58:24 +0000 |
711 | @@ -22,6 +22,8 @@ |
712 | |
713 | namespace Unity.ApplicationsLens.Utils |
714 | { |
715 | + private const string[] exponents = {"⁰", "¹", "²", "³", "⁴", |
716 | + "⁵", "⁶", "⁷", "⁸", "⁹"}; |
717 | |
718 | public AppInfo? get_app_info_for_actor (string actor) |
719 | { |
720 | @@ -161,5 +163,155 @@ |
721 | else |
722 | return pw.pw_dir + s.substring (k, -1); |
723 | } |
724 | - |
725 | + |
726 | + public string to_exponent (string power) |
727 | + { |
728 | + string result = ""; |
729 | + string pow = power; |
730 | + |
731 | + if (pow[0] == '-') |
732 | + { |
733 | + result += "⁻"; |
734 | + pow = pow [1:pow.length]; |
735 | + } |
736 | + |
737 | + // FIXME : a for loop would probably be faster, with no modifications on pow |
738 | + while (pow.length != 0) |
739 | + { |
740 | + result += exponents[int.parse(pow[0:1])]; |
741 | + pow = pow[1:pow.length]; |
742 | + } |
743 | + |
744 | + return result; |
745 | + } |
746 | + |
747 | + // Corrects precision error (due to the double type) |
748 | + /* Those function are not particularly elegant, and they |
749 | + * induce errors about approximations (we can display an approximate |
750 | + * equal sign for an exact equality, and vice versa). |
751 | + * It is related to the type we use for number representation (double), and |
752 | + * to the fact that this calculator is not intended to be a scientific calculator. |
753 | + * Anyway, if a bignum lib binding was available, it would be great to port this calculator |
754 | + * to this library. */ |
755 | + public string format_number (double _num, int maxprec, int maxdisplayprec, out bool approx) |
756 | + { |
757 | + string num = _num.to_string(); |
758 | + |
759 | + if (num.to_string().contains("e")) |
760 | + { |
761 | + string factor = _num.to_string().split("e")[0]; |
762 | + int power = int.parse(_num.to_string().split("e")[1]); |
763 | + |
764 | + return format_sci_number_s (ref factor, power, maxprec, maxdisplayprec, out approx); |
765 | + } |
766 | + else |
767 | + return format_number_s (ref num, maxprec, maxdisplayprec, out approx); |
768 | + } |
769 | + |
770 | + public string format_sci_number_s (ref string factor, int power, int maxprec, int maxdisplayprec, out bool approx) |
771 | + { |
772 | + string end_string = "×10" + to_exponent (power.to_string()); |
773 | + |
774 | + string ffactor = format_number_s(ref factor, maxprec, maxdisplayprec - end_string.length, out approx); |
775 | + |
776 | + // FIXME : if power is one digit-longer, this can fail (there's less space for ffactor) |
777 | + if (ffactor.length > 1 && ffactor[1] != '.') |
778 | + { |
779 | + ffactor = ffactor[0].to_string() + "." + ffactor[1:ffactor.length].replace(".", ""); |
780 | + remove_trailing_0 (ref ffactor); |
781 | + power++; |
782 | + } |
783 | + return ffactor + "×10" + to_exponent (power.to_string()); |
784 | + } |
785 | + |
786 | + public string format_number_s (ref string num, int maxprec, int maxdisplayprec, out bool approx) |
787 | + { |
788 | + bool isint = !num.contains("."); |
789 | + |
790 | + if (isint) maxprec--; |
791 | + |
792 | + // Too long integer, we have to transform it into scientific notation |
793 | + if (isint && (num.length > maxprec || num.length > maxdisplayprec)) |
794 | + { |
795 | + int power = num.length - 1; |
796 | + num = num[0].to_string() + "." + num[1 : maxprec + 1]; |
797 | + approx = true; |
798 | + return format_sci_number_s (ref num, power, maxprec, maxdisplayprec, out approx); |
799 | + } |
800 | + |
801 | + if (num.length > maxprec + 1) |
802 | + { |
803 | + num = num[0 : maxprec + 1]; |
804 | + approx = true; |
805 | + } |
806 | + |
807 | + round (ref num, maxprec); |
808 | + |
809 | + if (num.length > maxdisplayprec) |
810 | + { |
811 | + approx = true; |
812 | + round (ref num, maxdisplayprec); |
813 | + } |
814 | + |
815 | + return num; |
816 | + } |
817 | + |
818 | + public void round (ref string number, int size) |
819 | + { |
820 | + if (number.length <= size) return; |
821 | + |
822 | + string last_char = number[size].to_string(); |
823 | + |
824 | + number = number[0 : size]; |
825 | + |
826 | + if (int.parse(last_char) >= 5) |
827 | + add_1_remove_trailing_9 (ref number); |
828 | + else |
829 | + remove_trailing_0 (ref number); |
830 | + } |
831 | + |
832 | + public void remove_trailing_0 (ref string number) |
833 | + { |
834 | + for (int i = number.length - 1; i > 0; i--) |
835 | + { |
836 | + if (number[i].isdigit() && number[i] != '0') |
837 | + break; |
838 | + if (number[i] == '.') |
839 | + { |
840 | + number = number[0:number.length - 1]; |
841 | + break; |
842 | + } |
843 | + |
844 | + number = number[0:number.length - 1]; |
845 | + } |
846 | + } |
847 | + |
848 | + // Remove any trailing '9' (or replace it by zero, in the integer part) |
849 | + // and increment last non-9 char |
850 | + public void add_1_remove_trailing_9 (ref string number) |
851 | + { |
852 | + int i; |
853 | + int int_part_begin = -1; |
854 | + |
855 | + for (i = number.length - 1; i >= 0; i--) |
856 | + { |
857 | + if (number[i] == '.') |
858 | + { |
859 | + int_part_begin = i - 1; |
860 | + continue; |
861 | + } |
862 | + |
863 | + if (number[i].isdigit() && number[i] != '9') |
864 | + break; |
865 | + } |
866 | + |
867 | + if (int_part_begin != -1) |
868 | + { |
869 | + if (i == -1) number = "1" + string.nfill (int_part_begin + 1, '0'); |
870 | + else number = number[0 : i] + (int.parse(number[i].to_string()) + 1).to_string() |
871 | + + string.nfill (int_part_begin - i, '0'); |
872 | + } |
873 | + else |
874 | + number = number[0:i] + (int.parse(number[i].to_string()) + 1).to_string(); |
875 | + } |
876 | } |
excellent work, sponsoring, thanks!