Merge lp:~toineo/unity-lens-applications/calculator into lp:unity-lens-applications

Proposed by toineo
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
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.

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.

To post a comment you must log in.
Revision history for this message
Didier Roche-Tolomelli (didrocks) wrote :

excellent work, sponsoring, thanks!

review: Approve
Revision history for this message
Didier Roche-Tolomelli (didrocks) wrote :

opss, wrong window, sorry, was looking at another merge, will look at the one later ;)

Revision history for this message
Didier Roche-Tolomelli (didrocks) :
review: Needs Fixing
Revision history for this message
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_result_output (search_string, result));
-> can you pease fix the ident?

575 + string uri;
576 + Icon icon;
577 + uri = "unity-calc";
578 + string result_string = _(format_result_output (search_string, result));
-> 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<string,DelegateWrapper1> TwoLettersFct;
64 + private Gee.HashMap<string,DelegateWrapper1> ThreeLettersFct;
65 + private Gee.HashMap<string,DelegateWrapper1> FourLettersFct;
66 + private Gee.HashMap<string,DelegateWrapper1> FiveLettersFct;

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!

Revision history for this message
toineo (toineo) wrote :

Didrocks, fix commited for this. Thank you for reviewing, and for your help !

Revision history for this message
toineo (toineo) wrote :

History should now show with an empty search, as in the trunk version.

Revision history for this message
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/functions. Moreover, it would be like reinventing the wheel...

If anyone had a solution or something that could help, it would be greatly appreciated !

(Sorry for my *bad* English...)

review: Needs Fixing
Revision history for this message
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 ;-))

Revision history for this message
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...)

Revision history for this message
toineo (toineo) wrote :

Now, the branch should be up-to-date with trunk.

(I switch to abstain because I cannot switch to "needs reviews")

review: Abstain

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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 }

Subscribers

People subscribed via source and target branches