From 406a8a3c8eab4691ff32271ca906e93556d845dc Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Thu, 31 Dec 2020 12:42:08 +0100
Subject: Completed the Python plugin displaying file entropy.

---
 plugins/python/cglimpse/core.py    |  19 ++++-
 plugins/python/cglimpse/distro.py  |  61 +++++++++-------
 plugins/python/cglimpse/entropy.py | 137 ++++++++++++++++++++++++++----------
 plugins/python/cglimpse/method.py  |  10 ++-
 plugins/python/cglimpse/panel.py   | 130 ++++++++++++++++++++++++++++------
 plugins/python/cglimpse/panel.ui   | 140 ++++++++++++++++++++++++++++---------
 6 files changed, 380 insertions(+), 117 deletions(-)

diff --git a/plugins/python/cglimpse/core.py b/plugins/python/cglimpse/core.py
index d2cfdc4..29fb535 100644
--- a/plugins/python/cglimpse/core.py
+++ b/plugins/python/cglimpse/core.py
@@ -1,5 +1,6 @@
 
 from pychrysalide import PluginModule
+from pychrysalide.glibext import ConfigParam
 from pychrysalide.gui import core
 
 from .panel import CGlimpsePanel
@@ -13,7 +14,7 @@ class ContentGlimpse(PluginModule):
     _version = '0.1'
     _url = 'https://www.chrysalide.re/'
 
-    _actions = ( )
+    _actions = ( PluginModule.PluginAction.PLUGIN_LOADED, PluginModule.PluginAction.PANEL_CREATION, )
 
 
     def __init__(self):
@@ -22,3 +23,19 @@ class ContentGlimpse(PluginModule):
         super(ContentGlimpse, self).__init__()
 
         core.register_panel(CGlimpsePanel)
+
+
+    def _manage(self, action):
+        """React to several steps of the plugin life."""
+
+        CGlimpsePanel.setup_config(self.config)
+
+        return True
+
+
+    def _on_panel_creation(self, action, item):
+        """Get notified of a new panel creation."""
+
+        if type(item) == CGlimpsePanel:
+
+            item.attach_config(self.config)
diff --git a/plugins/python/cglimpse/distro.py b/plugins/python/cglimpse/distro.py
index 5ffc523..4b490e6 100644
--- a/plugins/python/cglimpse/distro.py
+++ b/plugins/python/cglimpse/distro.py
@@ -1,20 +1,28 @@
 
+from gi.repository import Gdk
+from pychrysalide.glibext import ConfigParam
+
 from .method import GlimpseMethod
 
 
 class ByteDistribution(GlimpseMethod):
 
-    def __init__(self, builder):
-        """Prepare a Shannon entropy display."""
 
-        super(ByteDistribution, self).__init__(builder)
+    @staticmethod
+    def setup_config(config):
+        """Register the configuration parameters for the method."""
 
-        button = builder.get_object('shannon_color')
-        button.connect('color-set', self._on_color_set)
+        color = Gdk.RGBA()
+        color.parse('#3465A4')
+
+        param = ConfigParam('cglimpse.distrib.color', ConfigParam.ConfigParamType.COLOR, color)
+        config.add(param)
 
-        self._on_color_set(button)
 
-        self._step = 0x80
+    def __init__(self, builder, config, update_cb):
+        """Prepare a Distrib entropy display."""
+
+        super(ByteDistribution, self).__init__(builder, config, update_cb)
 
         self._v_legend = 'Quantity'
         self._h_legend = 'Byte values'
@@ -24,41 +32,42 @@ class ByteDistribution(GlimpseMethod):
 
         self._values = {}
 
+        button = builder.get_object('distrib_color')
+        button.connect('color-set', self._on_color_set)
 
-    def _on_color_set(self, button):
-        """React on color chosen for the rendering."""
+        param = config.search('cglimpse.distrib.color')
+        color = param.value
 
-        color = button.get_rgba()
         self._color = [ color.red, color.green, color.blue, color.alpha ]
         self._shadow_color = [ color.red * 0.5, color.green * 0.5, color.blue * 0.5, color.alpha ]
 
+        button.set_rgba(param.value)
 
-    def format_legend(self, value, vert):
-        """Build the label used for a rule."""
 
-        if vert:
-            text = str(value)
+    def _on_color_set(self, button):
+        """React on color chosen for the rendering."""
 
-        else:
+        color = button.get_rgba()
 
-            scale = [ ' kb', ' Mb', ' Gb', ' Tb' ]
-            suffix = ''
+        self._color = [ color.red, color.green, color.blue, color.alpha ]
+        self._shadow_color = [ color.red * 0.5, color.green * 0.5, color.blue * 0.5, color.alpha ]
+
+        param = self._config.search('cglimpse.distrib.color')
+        param.value = color
 
-            for i in range(len(scale)):
+        self._update_cb()
 
-                if value < 1024:
-                    break
 
-                value /= 1024
-                suffix = scale[i]
+    def format_legend(self, value, vert):
+        """Build the label used for a rule."""
 
-            text = '%u%s' % (value, suffix)
+        text = str(int(value))
 
         return text
 
 
-    def update(self, data):
-        """Provide a description for the method."""
+    def update(self, data, coverage):
+        """Compute internal values for the method."""
 
         max_count = 0
 
@@ -67,7 +76,7 @@ class ByteDistribution(GlimpseMethod):
         for i in range(256):
             self._values[i] = 0
 
-        for b in data:
+        for b in data[coverage[0] : coverage[1]]:
 
             if b in self._values.keys():
                 self._values[b] += 1
diff --git a/plugins/python/cglimpse/entropy.py b/plugins/python/cglimpse/entropy.py
index f090624..5c4df5a 100644
--- a/plugins/python/cglimpse/entropy.py
+++ b/plugins/python/cglimpse/entropy.py
@@ -1,22 +1,32 @@
 
 import math
+from gi.repository import Gdk
+from pychrysalide.glibext import ConfigParam
 
 from .method import GlimpseMethod
 
 
 class ShannonEntropy(GlimpseMethod):
 
-    def __init__(self, builder):
-        """Prepare a Shannon entropy display."""
 
-        super(ShannonEntropy, self).__init__(builder)
+    @staticmethod
+    def setup_config(config):
+        """Register the configuration parameters for the method."""
 
-        button = builder.get_object('shannon_color')
-        button.connect('color-set', self._on_color_set)
+        color = Gdk.RGBA()
+        color.parse('#3465A4')
 
-        self._on_color_set(button)
+        param = ConfigParam('cglimpse.shannon.color', ConfigParam.ConfigParamType.COLOR, color)
+        config.add(param)
 
-        self._step = 0x80
+        param = ConfigParam('cglimpse.shannon.step', ConfigParam.ConfigParamType.ULONG, 0x80)
+        config.add(param)
+
+
+    def __init__(self, builder, config, update_cb):
+        """Prepare a Shannon entropy display."""
+
+        super(ShannonEntropy, self).__init__(builder, config, update_cb)
 
         self._v_legend = 'Entropy'
         self._h_legend = 'Byte offsets'
@@ -24,17 +34,64 @@ class ShannonEntropy(GlimpseMethod):
         self._x_range = [ 0, 1024, 10240 ]
         self._y_range = [ 0.0, 0.25, 1.0 ]
 
-        self._size = None
+        self._coverage = None
         self._values = []
 
+        button = builder.get_object('shannon_color')
+        button.connect('color-set', self._on_color_set)
+
+        param = config.search('cglimpse.shannon.color')
+        color = param.value
+
+        self._color = [ color.red, color.green, color.blue, color.alpha ]
+        self._shadow_color = [ color.red * 0.5, color.green * 0.5, color.blue * 0.5, color.alpha ]
+
+        button.set_rgba(param.value)
+
+        scale = builder.get_object('shannon_step')
+        scale.connect('format-value', self._on_scale_format_value)
+        scale.connect('value-changed', self._on_scale_value_changed)
+
+        param = config.search('cglimpse.shannon.step')
+        step = param.value
+
+        self._step = step
+
+        scale.set_value(step)
+
 
     def _on_color_set(self, button):
         """React on color chosen for the rendering."""
 
         color = button.get_rgba()
+
         self._color = [ color.red, color.green, color.blue, color.alpha ]
         self._shadow_color = [ color.red * 0.5, color.green * 0.5, color.blue * 0.5, color.alpha ]
 
+        param = self._config.search('cglimpse.shannon.color')
+        param.value = color
+
+        self._update_cb()
+
+
+    def _on_scale_value_changed(self, scale):
+        """React when the range value changes."""
+
+        step = int(scale.get_value())
+
+        self._step = step
+
+        param = self._config.search('cglimpse.shannon.step')
+        param.value = step
+
+        self._update_cb()
+
+
+    def _on_scale_format_value(self, scale, value):
+        """Change how the scale value is displayed."""
+
+        return '0x%x' % int(value)
+
 
     def format_legend(self, value, vert):
         """Build the label used for a rule."""
@@ -44,42 +101,53 @@ class ShannonEntropy(GlimpseMethod):
 
         else:
 
+            multi = 1
+
             scale = [ ' kb', ' Mb', ' Gb', ' Tb' ]
             suffix = ''
 
+            remaining = value
+
             for i in range(len(scale)):
 
-                if value < 1024:
+                if remaining < 1024:
                     break
 
-                value /= 1024
+                multi *= 1024
+
+                remaining /= 1024
                 suffix = scale[i]
 
-            text = '%u%s' % (value, suffix)
+            if value % multi == 0:
+                text = '%u%s' % (remaining, suffix)
+            else:
+                text = '%.1f%s' % (value / multi, suffix)
 
         return text
 
 
-    def update(self, data):
-        """Provide a description for the method."""
+    def update(self, data, coverage):
+        """Compute internal values for the method."""
 
-        self._size = len(data)
+        self._coverage = coverage
 
-        step = 2 ** math.ceil(math.log(self._size / 10, 2))
+        size = self._coverage[1] - self._coverage[0]
 
-        self._x_range = [ 0, step, 10 * step ]
+        step = math.ceil(size / 10)
+
+        self._x_range = [ coverage[0], step, coverage[0] + 10 * step ]
 
         self._values = []
 
-        for i in range(0, self._size, self._step):
+        for i in range(self._x_range[0], self._x_range[2], self._step):
 
             counter = [ 0 for i in range(256) ]
 
             start = i
             end = i + self._step
 
-            if end > self._size:
-                end = self._size
+            if end > self._coverage[1]:
+                end = self._coverage[1]
 
             for b in data[start : end]:
                 counter[b] += 1
@@ -97,31 +165,29 @@ class ShannonEntropy(GlimpseMethod):
     def render(self, cr, area):
         """Draw the bytes distribution for the current binary, if any."""
 
-        step = 2 ** math.ceil(math.log(self._size / 10, 2))
+        size = self._coverage[1] - self._coverage[0]
 
-        if self._size % step == 0:
-            full_size = self._size
-        else:
-            full_size = (self._size + step - 1) & ~(step - 1)
+        step = math.ceil(size / 10)
 
-        start = 0
+        start = self._coverage[0]
         last_x = area[0]
 
         last_y = area[1] + area[3] - (area[3] * self._values[0])
         cr.move_to(last_x, last_y + 2)
 
-        for i in range(0, self._size, self._step):
+        for i in range(self._x_range[0], self._x_range[2], self._step):
 
             end = i + self._step
 
-            if end > self._size:
-                end = self._size
+            if end > self._coverage[1]:
+                end = self._coverage[1]
 
-            x = area[0] + ((end - start) * area[2]) / full_size
-            y = area[1] + area[3] - (area[3] * self._values[int(i / self._step)])
+            x = area[0] + ((end - start) * area[2]) / (10 * step)
+            y = area[1] + area[3] - (area[3] * self._values[int((i - self._x_range[0]) / self._step)])
 
             if last_y != y:
                 cr.line_to(last_x, y + 2)
+                last_y = y
 
             cr.line_to(x, y + 2)
 
@@ -136,18 +202,19 @@ class ShannonEntropy(GlimpseMethod):
         last_y = area[1] + area[3] - (area[3] * self._values[0])
         cr.move_to(last_x, last_y)
 
-        for i in range(0, self._size, self._step):
+        for i in range(self._x_range[0], self._x_range[2], self._step):
 
             end = i + self._step
 
-            if end > self._size:
-                end = self._size
+            if end > self._coverage[1]:
+                end = self._coverage[1]
 
-            x = area[0] + ((end - start) * area[2]) / full_size
-            y = area[1] + area[3] - (area[3] * self._values[int(i / self._step)])
+            x = area[0] + ((end - start) * area[2]) / (10 * step)
+            y = area[1] + area[3] - (area[3] * self._values[int((i - self._x_range[0]) / self._step)])
 
             if last_y != y:
                 cr.line_to(last_x, y)
+                last_y = y
 
             cr.line_to(x, y)
 
diff --git a/plugins/python/cglimpse/method.py b/plugins/python/cglimpse/method.py
index f2e9fdb..731271e 100644
--- a/plugins/python/cglimpse/method.py
+++ b/plugins/python/cglimpse/method.py
@@ -3,10 +3,12 @@ class GlimpseMethod():
     """Abstract class for gimpses."""
 
 
-    def __init__(self, builder):
+    def __init__(self, builder, config, update_cb):
         """Populate the class with its attributes."""
 
         self._builder = builder
+        self._config = config
+        self._update_cb = update_cb
 
         self._v_legend = None
         self._h_legend = None
@@ -37,6 +39,12 @@ class GlimpseMethod():
         return str(value)
 
 
+    def update(self, data, coverage):
+        """Compute internal values for the method."""
+
+        pass
+
+
     def render(self, cr, area):
         """Draw the bytes distribution for the current binary, if any."""
 
diff --git a/plugins/python/cglimpse/panel.py b/plugins/python/cglimpse/panel.py
index 2245e3a..a195b0d 100644
--- a/plugins/python/cglimpse/panel.py
+++ b/plugins/python/cglimpse/panel.py
@@ -2,6 +2,7 @@
 import os
 from gi.repository import Gtk
 from pychrysalide import core
+from pychrysalide.glibext import ConfigParam
 from pychrysalide.gtkext import BuiltNamedWidget
 from pychrysalide.gtkext import EasyGtk
 from pychrysalide.gui import PanelItem
@@ -21,6 +22,18 @@ class CGlimpsePanel(PanelItem, UpdatablePanel):
     _working_group_id = core.setup_tiny_global_work_group()
 
 
+    @staticmethod
+    def setup_config(config):
+        """Register the configuration parameters for all the methods."""
+
+        param = ConfigParam('cglimpse.selected', ConfigParam.ConfigParamType.ULONG, 0)
+        config.add(param)
+
+        ShannonEntropy.setup_config(config)
+
+        ByteDistribution.setup_config(config)
+
+
     def __init__(self):
         """Initialize the GUI panel."""
 
@@ -35,10 +48,35 @@ class CGlimpsePanel(PanelItem, UpdatablePanel):
 
         self._builder.connect_signals(self)
 
+        self._start_changed_sid = -1
+        self._end_changed_sid = -1
+
         self._content = None
+        self._start = 0
+        self._end = 0
+
+        self._config = None
+        self._methods = {}
+        self._current = None
+
+
+    def attach_config(self, config):
+        """Attach a loaded configuration to the displayed panel."""
+
+        self._config = config
+
+        self._methods['shannon'] = ShannonEntropy(self._builder, config, self._force_update)
+        self._methods['distrib'] = ByteDistribution(self._builder, config, self._force_update)
+
+        param = config.search('cglimpse.selected')
+        selected = param.value
 
         combo = self._builder.get_object('method_sel')
-        self._on_method_changed(combo)
+
+        if selected == combo.get_active():
+            self._on_method_changed(combo)
+        else:
+            combo.set_active(selected)
 
 
     def _change_content(self, old, new):
@@ -46,6 +84,23 @@ class CGlimpsePanel(PanelItem, UpdatablePanel):
 
         self._content = new.content if new else None
 
+        length = len(self._content.data) if self._content else 1
+
+        self._start = 0
+        self._end = length
+
+        scale = self._builder.get_object('start_pos')
+        scale.set_range(0, length - 1)
+        scale.set_value(0)
+
+        self._start_changed_sid = scale.connect('value-changed', self._on_position_value_changed)
+
+        scale = self._builder.get_object('end_pos')
+        scale.set_range(1, length)
+        scale.set_value(length)
+
+        self._end_changed_sid = scale.connect('value-changed', self._on_position_value_changed)
+
         combo = self._builder.get_object('method_sel')
         self._on_method_changed(combo)
 
@@ -71,7 +126,7 @@ class CGlimpsePanel(PanelItem, UpdatablePanel):
 
         assert(uid == 0)
 
-        self._current.update(self._content.data)
+        self._current.update(self._content.data, [ self._start, self._end ])
 
         area = self._builder.get_object('content')
         area.queue_draw()
@@ -93,6 +148,28 @@ class CGlimpsePanel(PanelItem, UpdatablePanel):
         assert(uid == 0)
 
 
+    def _on_options_toggled(self, button):
+        """React on options display/hide order."""
+
+        lbl = self._builder.get_object('options_label')
+
+        common = self._builder.get_object('common_options')
+
+        if button.get_active():
+
+            button.get_parent().child_set_property(button, 'expand', False)
+            lbl.set_angle(0)
+
+            common.show()
+
+        else:
+
+            button.get_parent().child_set_property(button, 'expand', True)
+            lbl.set_angle(90)
+
+            common.hide()
+
+
     def _on_method_changed(self, combo):
         """React on method selection change."""
 
@@ -103,40 +180,49 @@ class CGlimpsePanel(PanelItem, UpdatablePanel):
             model = combo.get_model()
             key = model[tree_iter][1]
 
-            if key == 'shanon':
-                self._current = ShannonEntropy(self._builder)
-
-            else:
-                assert(key == 'distrib')
-                self._current = ByteDistribution(self._builder)
+            assert(key in self._methods.keys())
 
+            self._current = self._methods[key]
             self._current.switch()
 
-            if self._content:
+            stack = self._builder.get_object('specific_options')
+            stack.set_visible_child_name('%s_page' % key)
 
-                self.run_update(0)
+            param = self._config.search('cglimpse.selected')
+            param.value = combo.get_active()
 
+            self._force_update()
 
-    def _on_options_toggled(self, button):
-        """React on options display/hide order."""
 
-        lbl = self._builder.get_object('options_label')
+    def _on_position_value_changed(self, scale):
+        """React when the data range value changes."""
 
-        common = self._builder.get_object('common_options')
+        start_scale = self._builder.get_object('start_pos')
+        self._start = int(start_scale.get_value())
 
-        if button.get_active():
+        end_scale = self._builder.get_object('end_pos')
+        self._end = int(end_scale.get_value())
 
-            button.get_parent().child_set_property(button, 'expand', False)
-            lbl.set_angle(0)
+        length = len(self._content.data) if self._content else 1
 
-            common.show()
+        start_scale.disconnect(self._start_changed_sid)
+        end_scale.disconnect(self._end_changed_sid)
 
-        else:
+        start_scale.set_range(0, self._end - 1)
+        end_scale.set_range(self._start + 1, length)
 
-            button.get_parent().child_set_property(button, 'expand', True)
-            lbl.set_angle(90)
+        self._start_changed_sid = start_scale.connect('value-changed', self._on_position_value_changed)
+        self._end_changed_sid = end_scale.connect('value-changed', self._on_position_value_changed)
 
-            common.hide()
+        self._force_update()
+
+
+    def _force_update(self):
+        """Force the update of the rendering."""
+
+        if self._content:
+
+            self.run_update(0)
 
 
     def _render_grid(self, widget, cr):
diff --git a/plugins/python/cglimpse/panel.ui b/plugins/python/cglimpse/panel.ui
index 0ec6dc5..8299484 100644
--- a/plugins/python/cglimpse/panel.ui
+++ b/plugins/python/cglimpse/panel.ui
@@ -2,6 +2,22 @@
 <!-- Generated with glade 3.37.0 -->
 <interface>
   <requires lib="gtk+" version="3.24"/>
+  <object class="GtkAdjustment" id="end_pos_adj">
+    <property name="upper">100</property>
+    <property name="step-increment">1</property>
+    <property name="page-increment">10</property>
+  </object>
+  <object class="GtkAdjustment" id="shannon_step_adj">
+    <property name="lower">16</property>
+    <property name="upper">1024</property>
+    <property name="value">128</property>
+    <property name="step-increment">1</property>
+  </object>
+  <object class="GtkAdjustment" id="start_pos_adj">
+    <property name="upper">100</property>
+    <property name="step-increment">1</property>
+    <property name="page-increment">10</property>
+  </object>
   <object class="GtkBox" id="box">
     <property name="visible">True</property>
     <property name="can-focus">False</property>
@@ -38,22 +54,24 @@
             <property name="margin-end">8</property>
             <property name="margin-top">8</property>
             <property name="margin-bottom">8</property>
+            <property name="hexpand">False</property>
             <property name="orientation">vertical</property>
             <property name="spacing">8</property>
             <child>
-              <!-- n-columns=2 n-rows=3 -->
               <object class="GtkGrid">
                 <property name="visible">True</property>
                 <property name="can-focus">False</property>
+                <property name="hexpand">True</property>
                 <property name="row-spacing">8</property>
                 <property name="column-spacing">8</property>
                 <child>
                   <object class="GtkComboBoxText" id="method_sel">
                     <property name="visible">True</property>
                     <property name="can-focus">False</property>
+                    <property name="hexpand">True</property>
                     <property name="active">0</property>
                     <items>
-                      <item id="shanon" translatable="yes">Shanon entropy</item>
+                      <item id="shannon" translatable="yes">Shannon entropy</item>
                       <item id="distrib" translatable="yes">Bytes distribution</item>
                     </items>
                     <signal name="changed" handler="_on_method_changed" swapped="no"/>
@@ -89,9 +107,16 @@
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkSpinButton">
+                  <object class="GtkScale" id="start_pos">
+                    <property name="width-request">200</property>
                     <property name="visible">True</property>
                     <property name="can-focus">True</property>
+                    <property name="can-default">True</property>
+                    <property name="adjustment">start_pos_adj</property>
+                    <property name="show-fill-level">True</property>
+                    <property name="round-digits">0</property>
+                    <property name="digits">0</property>
+                    <property name="value-pos">right</property>
                   </object>
                   <packing>
                     <property name="left-attach">1</property>
@@ -99,9 +124,16 @@
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkSpinButton">
+                  <object class="GtkScale" id="end_pos">
+                    <property name="width-request">200</property>
                     <property name="visible">True</property>
                     <property name="can-focus">True</property>
+                    <property name="can-default">True</property>
+                    <property name="adjustment">end_pos_adj</property>
+                    <property name="show-fill-level">True</property>
+                    <property name="round-digits">0</property>
+                    <property name="digits">0</property>
+                    <property name="value-pos">right</property>
                   </object>
                   <packing>
                     <property name="left-attach">1</property>
@@ -116,28 +148,30 @@
               </packing>
             </child>
             <child>
-              <object class="GtkStack">
+              <object class="GtkSeparator">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkStack" id="specific_options">
                 <property name="visible">True</property>
                 <property name="can-focus">False</property>
+                <property name="hexpand">True</property>
                 <child>
-                  <!-- n-columns=2 n-rows=3 -->
                   <object class="GtkGrid">
                     <property name="visible">True</property>
                     <property name="can-focus">False</property>
                     <property name="row-spacing">8</property>
                     <property name="column-spacing">8</property>
                     <child>
-                      <object class="GtkSpinButton" id="shannon_step">
-                        <property name="visible">True</property>
-                        <property name="can-focus">True</property>
-                      </object>
-                      <packing>
-                        <property name="left-attach">1</property>
-                        <property name="top-attach">2</property>
-                      </packing>
-                    </child>
-                    <child>
                       <object class="GtkColorButton" id="shannon_color">
+                        <property name="width-request">200</property>
                         <property name="visible">True</property>
                         <property name="can-focus">True</property>
                         <property name="receives-default">True</property>
@@ -145,26 +179,26 @@
                       </object>
                       <packing>
                         <property name="left-attach">1</property>
-                        <property name="top-attach">1</property>
+                        <property name="top-attach">0</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkSeparator">
+                      <object class="GtkLabel">
                         <property name="visible">True</property>
                         <property name="can-focus">False</property>
-                        <property name="orientation">vertical</property>
+                        <property name="label" translatable="yes">Color:</property>
+                        <property name="xalign">1</property>
                       </object>
                       <packing>
                         <property name="left-attach">0</property>
                         <property name="top-attach">0</property>
-                        <property name="width">2</property>
                       </packing>
                     </child>
                     <child>
                       <object class="GtkLabel">
                         <property name="visible">True</property>
                         <property name="can-focus">False</property>
-                        <property name="label" translatable="yes">Color:</property>
+                        <property name="label" translatable="yes">Step:</property>
                         <property name="xalign">1</property>
                       </object>
                       <packing>
@@ -173,28 +207,67 @@
                       </packing>
                     </child>
                     <child>
+                      <object class="GtkScale" id="shannon_step">
+                        <property name="visible">True</property>
+                        <property name="can-focus">True</property>
+                        <property name="can-default">True</property>
+                        <property name="adjustment">shannon_step_adj</property>
+                        <property name="show-fill-level">True</property>
+                        <property name="digits">0</property>
+                        <property name="value-pos">right</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="name">shannon_page</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkGrid">
+                    <property name="visible">True</property>
+                    <property name="can-focus">False</property>
+                    <property name="row-spacing">8</property>
+                    <property name="column-spacing">8</property>
+                    <child>
+                      <object class="GtkColorButton" id="distrib_color">
+                        <property name="width-request">200</property>
+                        <property name="visible">True</property>
+                        <property name="can-focus">True</property>
+                        <property name="receives-default">True</property>
+                        <property name="rgba">rgb(52,101,164)</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
                       <object class="GtkLabel">
                         <property name="visible">True</property>
                         <property name="can-focus">False</property>
-                        <property name="label" translatable="yes">Step:</property>
+                        <property name="label" translatable="yes">Color:</property>
                         <property name="xalign">1</property>
                       </object>
                       <packing>
                         <property name="left-attach">0</property>
-                        <property name="top-attach">2</property>
+                        <property name="top-attach">0</property>
                       </packing>
                     </child>
                   </object>
                   <packing>
-                    <property name="name">page0</property>
-                    <property name="title" translatable="yes">page0</property>
+                    <property name="name">distrib_page</property>
+                    <property name="position">1</property>
                   </packing>
                 </child>
               </object>
               <packing>
                 <property name="expand">False</property>
                 <property name="fill">True</property>
-                <property name="position">1</property>
+                <property name="position">2</property>
               </packing>
             </child>
           </object>
@@ -204,9 +277,6 @@
             <property name="position">1</property>
           </packing>
         </child>
-        <child>
-          <placeholder/>
-        </child>
       </object>
       <packing>
         <property name="expand">False</property>
@@ -215,7 +285,6 @@
       </packing>
     </child>
     <child>
-      <!-- n-columns=2 n-rows=2 -->
       <object class="GtkGrid">
         <property name="visible">True</property>
         <property name="can-focus">False</property>
@@ -254,9 +323,9 @@
               <object class="GtkDrawingArea" id="content">
                 <property name="visible">True</property>
                 <property name="can-focus">False</property>
-                <property name="margin-start">4</property>
+                <property name="margin-start">8</property>
                 <property name="margin-end">8</property>
-                <property name="margin-top">8</property>
+                <property name="margin-top">4</property>
                 <property name="margin-bottom">4</property>
                 <property name="hexpand">True</property>
                 <property name="vexpand">True</property>
@@ -271,6 +340,12 @@
               <object class="GtkSpinner" id="mask">
                 <property name="visible">True</property>
                 <property name="can-focus">False</property>
+                <property name="margin-start">8</property>
+                <property name="margin-end">8</property>
+                <property name="margin-top">4</property>
+                <property name="margin-bottom">4</property>
+                <property name="hexpand">True</property>
+                <property name="vexpand">True</property>
                 <property name="active">True</property>
               </object>
               <packing>
@@ -292,6 +367,7 @@
       <packing>
         <property name="expand">True</property>
         <property name="fill">True</property>
+        <property name="pack-type">end</property>
         <property name="position">1</property>
       </packing>
     </child>
-- 
cgit v0.11.2-87-g4458