[gnome-calculator] WIP: Use GtkPopovers for memory and function buttons. Bug 748742.



commit ad6270b713da26c6b34a30934b4315c390bb007f
Author: Niels De Graef <nielsdegraef gmail com>
Date:   Mon Oct 24 13:28:00 2016 +0200

    WIP: Use GtkPopovers for memory and function buttons. Bug 748742.
    
    Signed-off-by: Niels De Graef <nielsdegraef gmail com>

 data/Makefile.am                    |    2 +
 data/buttons-advanced.ui            |    4 +-
 data/buttons-financial.ui           |    2 +-
 data/buttons-programming.ui         |    2 +-
 data/calculator.css                 |    6 +
 data/gnome-calculator.gresource.xml |    2 +
 data/math-function-popover.ui       |   76 ++++++++++++
 data/math-variable-popover.ui       |   67 ++++++++++
 lib/function-manager.vala           |   17 ++-
 lib/math-variables.vala             |   14 ++-
 po/POTFILES.in                      |    5 +-
 src/Makefile.am                     |    4 +-
 src/math-buttons.vala               |   40 +-----
 src/math-function-popover.vala      |  197 ++++++++++++++++++++++++++++++
 src/math-function-popup.vala        |  227 -----------------------------------
 src/math-variable-popover.vala      |  157 ++++++++++++++++++++++++
 src/math-variable-popup.vala        |  184 ----------------------------
 17 files changed, 550 insertions(+), 456 deletions(-)
---
diff --git a/data/Makefile.am b/data/Makefile.am
index a8ba11d..d44122e 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -21,6 +21,8 @@ EXTRA_DIST = \
        history-entry.ui \
        history-view.ui \
        math-converter.ui \
+       math-function-popover.ui \
+       math-variable-popover.ui \
        math-window.ui \
        math-shortcuts.ui \
        menu.ui \
diff --git a/data/buttons-advanced.ui b/data/buttons-advanced.ui
index 835c5b4..28ed249 100644
--- a/data/buttons-advanced.ui
+++ b/data/buttons-advanced.ui
@@ -567,7 +567,7 @@
       </packing>
     </child>
     <child>
-      <object class="GtkButton" id="calc_memory_button">
+      <object class="GtkMenuButton" id="calc_memory_button">
         <property name="visible">True</property>
         <property name="can_focus">True</property>
         <property name="receives_default">True</property>
@@ -943,7 +943,7 @@
       </packing>
     </child>
     <child>
-      <object class="GtkButton" id="calc_function_button">
+      <object class="GtkMenuButton" id="calc_function_button">
         <property name="visible">True</property>
         <property name="can_focus">True</property>
         <property name="receives_default">True</property>
diff --git a/data/buttons-financial.ui b/data/buttons-financial.ui
index e562ed5..17ff485 100644
--- a/data/buttons-financial.ui
+++ b/data/buttons-financial.ui
@@ -1846,7 +1846,7 @@
     <property name="column-homogeneous">True</property>
     <property name="column_spacing">4</property>
     <child>
-      <object class="GtkButton" id="calc_memory_button">
+      <object class="GtkMenuButton" id="calc_memory_button">
         <property name="visible">True</property>
         <property name="can_focus">True</property>
         <property name="receives_default">True</property>
diff --git a/data/buttons-programming.ui b/data/buttons-programming.ui
index 6379c27..24066b1 100644
--- a/data/buttons-programming.ui
+++ b/data/buttons-programming.ui
@@ -1849,7 +1849,7 @@
           </packing>
         </child>
         <child>
-          <object class="GtkButton" id="calc_memory_button">
+          <object class="GtkMenuButton" id="calc_memory_button">
             <property name="visible">True</property>
             <property name="can_focus">True</property>
             <property name="receives_default">True</property>
diff --git a/data/calculator.css b/data/calculator.css
index ab84348..97e241e 100644
--- a/data/calculator.css
+++ b/data/calculator.css
@@ -63,3 +63,9 @@ window > grid {
 
   .history-entry .answer-label {
   }
+
+/* Used for MathVariablePopover */
+row.popover-row {
+  border-bottom: 1px groove @borders;
+  min-height: 35px;
+}
diff --git a/data/gnome-calculator.gresource.xml b/data/gnome-calculator.gresource.xml
index bec25ea..403c045 100644
--- a/data/gnome-calculator.gresource.xml
+++ b/data/gnome-calculator.gresource.xml
@@ -8,6 +8,8 @@
     <file preprocess="xml-stripblanks">history-view.ui</file>
     <file preprocess="xml-stripblanks">history-entry.ui</file>
     <file preprocess="xml-stripblanks">math-converter.ui</file>
+    <file preprocess="xml-stripblanks">math-function-popover.ui</file>
+    <file preprocess="xml-stripblanks">math-variable-popover.ui</file>
     <file preprocess="xml-stripblanks">math-window.ui</file>
     <file preprocess="xml-stripblanks">math-shortcuts.ui</file>
     <file preprocess="xml-stripblanks">menu.ui</file>
diff --git a/data/math-function-popover.ui b/data/math-function-popover.ui
new file mode 100644
index 0000000..766ec55
--- /dev/null
+++ b/data/math-function-popover.ui
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.16"/>
+  <template class="MathFunctionPopover" parent="GtkPopover">
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkBox" id="vbox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkScrolledWindow" id="function_list_scrolled">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="height_request">200</property>
+            <property name="shadow_type">in</property>
+            <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+            <child>
+              <object class="GtkListBox" id="function_list">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="selection_mode">none</property>
+                <signal name="row_activated" handler="insert_function_cb" swapped="no"/>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkEntry" id="function_name_entry">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="placeholder_text" translatable="yes">New function</property>
+            <signal name="button_press_event" handler="function_name_mouse_click_cb" swapped="no"/>
+            <signal name="key_press_event" handler="function_name_key_press_cb" swapped="no"/>
+            <signal name="changed" handler="function_name_changed_cb" swapped="no"/>
+            <signal name="activate" handler="add_function_cb" swapped="no"/>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox" id="add_function_box">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">horizontal</property>
+            <property name="spacing">6</property>
+            <property name="tooltip_text">Select no. of arguments</property>
+            <child>
+              <object class="GtkSpinButton" id="add_arguments_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="add_function_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="sensitive">False</property>
+                <signal name="clicked" handler="add_function_cb" swapped="no"/>
+                <child>
+                  <object class="GtkImage" id="add_function_button_image">
+                    <property name="visible">True</property>
+                    <property name="icon_name">list-add-symbolic</property>
+                    <property name="pixel_size">16</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="pack_type">end</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/math-variable-popover.ui b/data/math-variable-popover.ui
new file mode 100644
index 0000000..62ab198
--- /dev/null
+++ b/data/math-variable-popover.ui
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.16"/>
+  <template class="MathVariablePopover" parent="GtkPopover">
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkBox" id="content">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="margin">3</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkScrolledWindow" id="variable_list_scrolled">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="height_request">150</property>
+            <property name="shadow_type">in</property>
+            <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+            <child>
+              <object class="GtkListBox" id="variable_list">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="selection_mode">none</property>
+                <signal name="row_activated" handler="insert_variable_cb" swapped="no"/>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox" id="add_variable_box">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">horizontal</property>
+            <child>
+              <object class="GtkEntry" id="variable_name_entry">
+                <property name="placeholder_text" translatable="yes">Variable name</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <signal name="key_press_event" handler="variable_name_key_press_cb" swapped="no"/>
+                <signal name="changed" handler="variable_name_changed_cb" swapped="no"/>
+                <signal name="activate" handler="store_variable_cb" swapped="no"/>
+              </object>
+            </child>
+            <child>
+              <object class="GtkButton" id="store_variable_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="sensitive">False</property>
+                <property name="tooltip_text" translatable="yes">Store value into existing or new 
variable</property>
+                <signal name="clicked" handler="store_variable_cb" swapped="no"/>
+                <child>
+                  <object class="GtkImage" id="store_variable_button_image">
+                    <property name="visible">True</property>
+                    <property name="icon_name">document-save-symbolic</property>
+                    <property name="pixel_size">16</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/lib/function-manager.vala b/lib/function-manager.vala
index d07c87a..61dd4ea 100644
--- a/lib/function-manager.vala
+++ b/lib/function-manager.vala
@@ -16,6 +16,10 @@ public class FunctionManager : Object
     private HashTable<string, MathFunction> functions;
     private Serializer serializer;
 
+    public signal void function_added (MathFunction function);
+    public signal void function_edited (MathFunction new_function);
+    public signal void function_deleted (MathFunction function);
+
     public FunctionManager ()
     {
         functions = new HashTable <string, MathFunction> (str_hash, str_equal);
@@ -159,7 +163,7 @@ public class FunctionManager : Object
         if (expression == null)
             return null;
 
-        i = left.index_of_char('(');
+        i = left.index_of_char ('(');
         if (i < 0)
             return null;
         var name = left.substring (0, i).strip ();
@@ -243,6 +247,11 @@ public class FunctionManager : Object
         return array_sort_string (names);
     }
 
+    /**
+      * Adds a function to the manager, unless the given name is already taken
+      * by a predefined function.
+      * @return If the function was successfully added.
+      */
     private bool add (MathFunction new_function)
     {
         MathFunction? existing_function = get (new_function.name);
@@ -250,10 +259,11 @@ public class FunctionManager : Object
         if (existing_function != null && !existing_function.is_custom_function ())
             return false;
 
+        functions[new_function.name] = new_function;
         if (existing_function != null)
-            functions.replace (new_function.name, new_function);
+            function_edited (new_function);
         else
-            functions.insert (new_function.name, new_function);
+            function_added (new_function);
 
         return true;
     }
@@ -291,6 +301,7 @@ public class FunctionManager : Object
         {
             functions.remove (name);
             save ();
+            function_deleted (function);
         }
     }
 
diff --git a/lib/math-variables.vala b/lib/math-variables.vala
index 63b7191..9685b2c 100644
--- a/lib/math-variables.vala
+++ b/lib/math-variables.vala
@@ -14,6 +14,10 @@ public class MathVariables : Object
     private HashTable<string, Number?> registers;
     private Serializer serializer;
 
+    public signal void variable_added (string name, Number value);
+    public signal void variable_edited (string name, Number new_value);
+    public signal void variable_deleted (string name);
+
     public MathVariables ()
     {
         registers = new HashTable <string, Number?> (str_hash, str_equal);
@@ -138,18 +142,24 @@ public class MathVariables : Object
 
     public new void set (string name, Number value)
     {
-        registers.insert (name, value);
+        bool editing = registers.contains (name);
+        registers[name] = value;
         save ();
+        if (editing)
+            variable_edited (name, value);
+        else
+            variable_added (name, value);
     }
 
     public new Number? get (string name)
     {
-        return registers.lookup (name);
+        return registers[name];
     }
 
     public void delete (string name)
     {
         registers.remove (name);
         save ();
+        variable_deleted (name);
     }
 }
diff --git a/po/POTFILES.in b/po/POTFILES.in
index c393e8a..b58dc51 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -8,6 +8,8 @@
 data/gnome-calculator.appdata.xml.in
 data/gnome-calculator.desktop.in
 [type: gettext/glade]data/math-converter.ui
+[type: gettext/glade]data/math-function-popover.ui
+[type: gettext/glade]data/math-variable-popover.ui
 [type: gettext/glade]data/math-window.ui
 [type: gettext/glade]data/math-shortcuts.ui
 [type: gettext/glade]data/menu.ui
@@ -30,7 +32,8 @@ src/math-buttons.vala
 src/math-converter.vala
 src/math-display.vala
 src/math-preferences.vala
-src/math-variable-popup.vala
+src/math-function-popover.vala
+src/math-variable-popover.vala
 src/math-window.vala
 tests/test-equation.vala
 tests/test-number.vala
diff --git a/src/Makefile.am b/src/Makefile.am
index 6ad5a70..dbe0871 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -22,9 +22,9 @@ gnome_calculator_SOURCES = \
        math-converter.vala \
        math-display.vala \
        math-preferences.vala \
-       math-variable-popup.vala \
+       math-function-popover.vala \
+       math-variable-popover.vala \
        math-window.vala \
-       math-function-popup.vala \
        math-history.vala \
        $(BUILT_SOURCES)
 
diff --git a/src/math-buttons.vala b/src/math-buttons.vala
index 38d0f02..2e7f546 100644
--- a/src/math-buttons.vala
+++ b/src/math-buttons.vala
@@ -341,13 +341,7 @@ public class MathButtons : Gtk.Box
         }
 
         /* Configure buttons */
-        var button = builder.get_object ("calc_memory_button") as Gtk.Button;
-        if (button != null)
-            button.clicked.connect (on_memory);
-        button = builder.get_object ("calc_function_button") as Gtk.Button;
-        if (button != null)
-            button.clicked.connect (on_insert_function);
-        button = builder.get_object ("calc_numeric_point_button") as Gtk.Button;
+        var button = builder.get_object ("calc_numeric_point_button") as Gtk.Button;
         if (button != null)
             button.set_label (equation.serializer.get_radix ().to_string ());
 
@@ -357,6 +351,12 @@ public class MathButtons : Gtk.Box
         menu_button = builder.get_object ("calc_shift_right_button") as Gtk.MenuButton;
         if (menu_button != null)
             menu_button.menu_model = create_shift_menu (false);
+        menu_button = builder.get_object ("calc_memory_button") as Gtk.MenuButton;
+        if (menu_button != null)
+            menu_button.popover = new MathVariablePopover (equation);
+        menu_button = builder.get_object ("calc_function_button") as Gtk.MenuButton;
+        if (menu_button != null)
+            menu_button.popover = new MathFunctionPopover (equation);
 
         if (mode == ButtonMode.PROGRAMMING)
         {
@@ -455,19 +455,6 @@ public class MathButtons : Gtk.Box
         }
     }
 
-    private void on_memory (Gtk.Widget widget)
-    {
-        var popup = new MathVariablePopup (equation);
-        popup.set_transient_for (widget.get_toplevel () as Gtk.Window);
-
-        Gtk.Allocation allocation;
-        widget.get_allocation (out allocation);
-        int x, y;
-        widget.get_window ().get_root_coords (allocation.x, allocation.y, out x, out y);
-        popup.move (x, y);
-        popup.show ();
-    }
-
     private Menu create_shift_menu (bool shift_left)
     {
         var shift_menu = new Menu ();
@@ -485,19 +472,6 @@ public class MathButtons : Gtk.Box
         return shift_menu;
     }
 
-    private void on_insert_function (Gtk.Widget widget)
-    {
-        var popup = new MathFunctionPopup (equation);
-        popup.set_transient_for (widget.get_toplevel () as Gtk.Window);
-
-        Gtk.Allocation allocation;
-        widget.get_allocation (out allocation);
-        int x, y;
-        widget.get_window ().get_root_coords (allocation.x, allocation.y, out x, out y);
-        popup.move (x, y);
-        popup.show ();
-    }
-
     private void on_launch_finc_dialog (SimpleAction action, Variant? param)
     {
         var name = param.get_string ();
diff --git a/src/math-function-popover.vala b/src/math-function-popover.vala
new file mode 100644
index 0000000..e44c184
--- /dev/null
+++ b/src/math-function-popover.vala
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2013 Garima Joshi
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+[GtkTemplate (ui = "/org/gnome/calculator/math-function-popover.ui")]
+public class MathFunctionPopover : Gtk.Popover
+{
+    // Used to pretty print function arguments, e.g. f(x, y, z)
+    private static string[] FUNCTION_ARGS = {"x","y","z","u","v","w","a","b","c","d"};
+
+    private MathEquation equation;
+
+    [GtkChild]
+    private Gtk.ListBox function_list;
+
+    [GtkChild]
+    private Gtk.Entry function_name_entry;
+    private bool function_name_entry_placeholder_reseted = false;
+
+    [GtkChild]
+    private Gtk.Button add_function_button;
+    [GtkChild]
+    private Gtk.SpinButton add_arguments_button;
+
+    public MathFunctionPopover (MathEquation equation)
+    {
+        this.equation = equation;
+
+        FunctionManager function_manager = FunctionManager.get_default_function_manager ();
+        var names = function_manager.get_names ();
+
+        for (var i = 0; names[i] != null; i++)
+        {
+            var function = function_manager[names[i]];
+            function_list.add (make_function_row (function));
+        }
+
+        // Sort list
+        function_list.set_sort_func (function_list_sort);
+
+        function_manager.function_added.connect ((function) => {
+            function_list.add (make_function_row (function));
+        });
+        function_manager.function_edited.connect ((function) => {
+            function_list.remove (find_row_for_function (function));
+            function_list.add (make_function_row (function));
+        });
+        function_manager.function_deleted.connect ((function) => {
+            function_list.remove (find_row_for_function (function));
+        });
+
+        add_arguments_button.set_range (1, 10);
+        add_arguments_button.set_increments (1, 1);
+    }
+
+    private Gtk.ListBoxRow? find_row_for_function (MathFunction function)
+    {
+        weak Gtk.ListBoxRow? row = null;
+        function_list.foreach ((child) => {
+            if (function.name == child.get_data<MathFunction> ("function").name)
+                row = child as Gtk.ListBoxRow;
+        });
+        return row;
+    }
+
+    [GtkCallback]
+    private void insert_function_cb (Gtk.ListBoxRow row)
+    {
+        var function = row.get_data<MathFunction> ("function");
+        equation.insert (function.name + "()");
+
+        // Place the cursor between the parentheses after inserting the function
+        Gtk.TextIter end;
+        equation.get_iter_at_mark (out end, equation.get_insert ());
+        end.backward_chars (1);
+        equation.place_cursor (end);
+    }
+
+    [GtkCallback]
+    private bool function_name_mouse_click_cb (Gtk.Widget widget, Gdk.EventButton event)
+    {
+        if (!this.function_name_entry_placeholder_reseted)
+        {
+            this.function_name_entry_placeholder_reseted = true;
+            this.function_name_entry.text = "";
+        }
+
+        return false;
+    }
+
+    [GtkCallback]
+    private bool function_name_key_press_cb (Gtk.Widget widget, Gdk.EventKey event)
+    {
+        this.function_name_entry_placeholder_reseted = true;
+
+        /* Can't have whitespace in names, so replace with underscores */
+        if (event.keyval == Gdk.Key.space || event.keyval == Gdk.Key.KP_Space)
+            event.keyval = Gdk.Key.underscore;
+
+        return false;
+    }
+
+    [GtkCallback]
+    private void function_name_changed_cb ()
+    {
+        add_function_button.sensitive = function_name_entry.get_text () != "";
+    }
+
+    [GtkCallback]
+    private void add_function_cb (Gtk.Widget widget)
+    {
+        var name = function_name_entry.text;
+        if (name == "")
+            return;
+
+        var arguments = add_arguments_button.get_value_as_int ();
+        string formatted_args = "";
+        if (arguments > 0)
+            formatted_args = string.joinv ("; ", FUNCTION_ARGS[0:arguments]);
+
+        name += "(%s)=".printf(formatted_args);
+        equation.clear ();
+        equation.insert (name);
+    }
+
+    private void save_function_cb (Gtk.Widget widget)
+    {
+        var function = widget.get_data<MathFunction> ("function");
+        var function_to_edit = "%s(%s)=%s@%s".printf (function.name,
+                                                      string.joinv (";", function.arguments),
+                                                      function.expression,
+                                                      function.description);
+        equation.clear ();
+        equation.insert (function_to_edit);
+    }
+
+    private void delete_function_cb (Gtk.Widget widget)
+    {
+        var function = widget.get_data<MathFunction> ("function");
+
+        var function_manager = FunctionManager.get_default_function_manager ();
+        function_manager.delete (function.name);
+    }
+
+    private Gtk.ListBoxRow make_function_row (MathFunction function)
+    {
+        var row = new Gtk.ListBoxRow ();
+        row.get_style_context ().add_class ("popover-row");
+        row.set_data<MathFunction> ("function", function);
+        row.set_tooltip_text ("%s".printf (function.description));
+
+        var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
+
+        var expression = "(x)";
+        if (function.is_custom_function ())
+            expression = "(%s)".printf (string.joinv (";", function.arguments));
+
+        var label = new Gtk.Label ("<b>%s</b>%s".printf (function.name, expression));
+        label.set_use_markup (true);
+        label.halign = Gtk.Align.START;
+        hbox.pack_start (label, true, true, 0);
+
+        if (function.is_custom_function ())
+        {
+            var button = new Gtk.Button.from_icon_name ("edit-symbolic");
+            button.get_style_context ().add_class ("flat");
+            button.set_data<MathFunction> ("function", function);
+            button.clicked.connect (save_function_cb);
+            hbox.pack_start (button, false, true, 0);
+
+            button = new Gtk.Button.from_icon_name ("list-remove-symbolic");
+            button.get_style_context ().add_class ("flat");
+            button.set_data<MathFunction> ("function", function);
+            button.clicked.connect (delete_function_cb);
+            hbox.pack_start (button, false, true, 0);
+        }
+
+        row.add (hbox);
+        row.show_all ();
+
+        return row;
+    }
+
+    private int function_list_sort (Gtk.ListBoxRow row1, Gtk.ListBoxRow row2)
+    {
+        var function1 = row1.get_data<MathFunction> ("function");
+        var function2 = row2.get_data<MathFunction> ("function");
+
+        return strcmp (function1.name, function2.name);
+    }
+}
diff --git a/src/math-variable-popover.vala b/src/math-variable-popover.vala
new file mode 100644
index 0000000..b5c7c31
--- /dev/null
+++ b/src/math-variable-popover.vala
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2008-2012 Robert Ancell
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+[GtkTemplate (ui = "/org/gnome/calculator/math-variable-popover.ui")]
+public class MathVariablePopover : Gtk.Popover
+{
+    private static string[] RESERVED_VARIABLE_NAMES = {"ans", "rand"};
+
+    private MathEquation equation;
+
+    [GtkChild]
+    private Gtk.ListBox variable_list;
+    [GtkChild]
+    private Gtk.Entry variable_name_entry;
+    [GtkChild]
+    private Gtk.Button store_variable_button;
+
+    public MathVariablePopover (MathEquation equation)
+    {
+        this.equation = equation;
+
+        // Fill variable list
+        var names = equation.variables.get_names ();
+        for (var i = 0; names[i] != null; i++)
+        {
+            var value = equation.variables[names[i]];
+            variable_list.add (make_variable_row (names[i], value));
+        }
+
+        variable_list.add (make_variable_row ("rand", null));
+        variable_list.add (make_variable_row ("ans", equation.answer));
+
+        // Sort list
+        variable_list.set_sort_func (variable_list_sort);
+
+        // Listen for variable changes
+        equation.variables.variable_added.connect ((name, value) => {
+            variable_list.add (make_variable_row (name, value));
+        });
+        equation.variables.variable_edited.connect ((name, value) => {
+            variable_list.remove (find_row_for_variable (name));
+            variable_list.add (make_variable_row (name, value));
+        });
+        equation.variables.variable_deleted.connect ((name) => {
+            variable_list.remove (find_row_for_variable (name));
+        });
+    }
+
+    private Gtk.ListBoxRow? find_row_for_variable (string name)
+    {
+        weak Gtk.ListBoxRow? row = null;
+        variable_list.foreach ((child) => {
+            if (name == child.get_data<string> ("variable_name"))
+                row = child as Gtk.ListBoxRow;
+        });
+        return row;
+    }
+
+    [GtkCallback]
+    private void insert_variable_cb (Gtk.ListBoxRow row)
+    {
+        var name = row.get_data<string> ("variable_name");
+        equation.insert (name);
+    }
+
+    [GtkCallback]
+    private bool variable_name_key_press_cb (Gtk.Widget widget, Gdk.EventKey event)
+    {
+        /* Can't have whitespace in names, so replace with underscores */
+        if (event.keyval == Gdk.Key.space || event.keyval == Gdk.Key.KP_Space)
+            event.keyval = Gdk.Key.underscore;
+
+        return false;
+    }
+
+    [GtkCallback]
+    private void variable_name_changed_cb ()
+    {
+        store_variable_button.sensitive = (variable_name_entry.get_text () != "");
+    }
+
+    [GtkCallback]
+    private void store_variable_cb (Gtk.Widget widget)
+    {
+        var name = variable_name_entry.get_text ();
+        if (name == "" || name in RESERVED_VARIABLE_NAMES)
+            return;
+
+        var z = equation.number;
+        if (z != null)
+            equation.variables[name] = z;
+        else if (equation.is_result)
+            equation.variables[name] = equation.answer;
+        else
+            warning ("Can't add variable %s, the display is not a number", name);
+
+        variable_name_entry.set_text ("");
+    }
+
+    private void delete_variable_cb (Gtk.Widget widget)
+    {
+        var name = widget.get_data<string> ("variable_name");
+        equation.variables.delete (name);
+    }
+
+    private Gtk.ListBoxRow make_variable_row (string name, Number? value)
+    {
+        var row = new Gtk.ListBoxRow ();
+        row.get_style_context ().add_class ("popover-row");
+        row.set_data<string> ("variable_name", name);
+
+        var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
+
+        string text;
+        if (value != null)
+        {
+            var value_text = equation.serializer.to_string (value);
+            text = "<b>%s</b> = %s".printf (name, value_text);
+        }
+        else
+            text = "<b>%s</b>".printf (name);
+
+        var label = new Gtk.Label (text);
+        label.set_use_markup (true);
+        label.halign = Gtk.Align.START;
+        hbox.pack_start (label, true, true, 0);
+
+        if (!(name in RESERVED_VARIABLE_NAMES))
+        {
+            var button = new Gtk.Button.from_icon_name ("list-remove-symbolic");
+            button.get_style_context ().add_class ("flat");
+            button.set_data<string> ("variable_name", name);
+            button.clicked.connect (delete_variable_cb);
+            hbox.pack_start (button, false, true, 0);
+        }
+
+        row.add (hbox);
+        row.show_all ();
+
+        return row;
+    }
+
+    private int variable_list_sort (Gtk.ListBoxRow row1, Gtk.ListBoxRow row2)
+    {
+        string name1 = row1.get_data<string> ("variable_name");
+        string name2 = row2.get_data<string> ("variable_name");
+
+        return strcmp (name1, name2);
+    }
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]