summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCyrille Bagard <nocbos@gmail.com>2020-12-31 11:42:08 (GMT)
committerCyrille Bagard <nocbos@gmail.com>2020-12-31 12:39:08 (GMT)
commit406a8a3c8eab4691ff32271ca906e93556d845dc (patch)
tree7bb5d771804c0a006ab8a1f474750a34e48cd018
parent8965133f188c817cbdf4fcf9d1f1b60462bbfe7d (diff)
Completed the Python plugin displaying file entropy.
-rw-r--r--plugins/python/cglimpse/core.py19
-rw-r--r--plugins/python/cglimpse/distro.py61
-rw-r--r--plugins/python/cglimpse/entropy.py137
-rw-r--r--plugins/python/cglimpse/method.py10
-rw-r--r--plugins/python/cglimpse/panel.py130
-rw-r--r--plugins/python/cglimpse/panel.ui140
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>