summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xgrc/checks.py11
-rw-r--r--grc/core/Block.py530
-rw-r--r--grc/core/Config.py1
-rw-r--r--grc/core/Connection.py119
-rw-r--r--grc/core/Constants.py29
-rw-r--r--grc/core/Element.py96
-rw-r--r--grc/core/FlowGraph.py157
-rw-r--r--grc/core/Messages.py3
-rw-r--r--grc/core/Param.py153
-rw-r--r--grc/core/ParseXML.py67
-rw-r--r--grc/core/Platform.py84
-rw-r--r--grc/core/Port.py107
-rw-r--r--grc/core/generator/FlowGraphProxy.py18
-rw-r--r--grc/core/generator/Generator.py91
-rw-r--r--grc/core/generator/__init__.py3
-rw-r--r--grc/core/generator/flow_graph.tmpl10
-rw-r--r--grc/core/utils/__init__.py9
-rw-r--r--grc/core/utils/complexity.py13
-rw-r--r--grc/core/utils/epy_block_io.py11
-rw-r--r--grc/core/utils/expr_utils.py20
-rw-r--r--grc/core/utils/extract_docs.py28
-rw-r--r--grc/core/utils/odict.py115
-rw-r--r--grc/gui/ActionHandler.py215
-rw-r--r--grc/gui/Actions.py215
-rw-r--r--grc/gui/Bars.py70
-rw-r--r--grc/gui/Block.py291
-rw-r--r--grc/gui/BlockTreeWindow.py203
-rw-r--r--grc/gui/Colors.py119
-rw-r--r--grc/gui/Config.py7
-rw-r--r--grc/gui/Connection.py78
-rw-r--r--grc/gui/Constants.py21
-rw-r--r--grc/gui/Dialogs.py118
-rw-r--r--grc/gui/DrawingArea.py195
-rw-r--r--grc/gui/Element.py45
-rw-r--r--grc/gui/Executor.py17
-rw-r--r--grc/gui/FileDialogs.py67
-rw-r--r--grc/gui/FlowGraph.py232
-rw-r--r--grc/gui/MainWindow.py200
-rw-r--r--grc/gui/NotebookPage.py183
-rw-r--r--grc/gui/Param.py459
-rw-r--r--grc/gui/ParamWidgets.py300
-rw-r--r--grc/gui/ParserErrorsDialog.py37
-rw-r--r--grc/gui/Platform.py4
-rw-r--r--grc/gui/Port.py105
-rw-r--r--grc/gui/Preferences.py21
-rw-r--r--grc/gui/PropsDialog.py202
-rw-r--r--grc/gui/StateCache.py5
-rw-r--r--grc/gui/Utils.py65
-rw-r--r--grc/gui/VariableEditor.py130
-rw-r--r--grc/gui/external_editor.py6
-rwxr-xr-xgrc/main.py9
51 files changed, 2513 insertions, 2781 deletions
diff --git a/grc/checks.py b/grc/checks.py
index fd0e5de06a..40a0b2b270 100755
--- a/grc/checks.py
+++ b/grc/checks.py
@@ -15,6 +15,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+from __future__ import absolute_import
import os
import warnings
@@ -51,10 +52,12 @@ def die(error, message):
def check_gtk():
try:
warnings.filterwarnings("error")
- import pygtk
- pygtk.require('2.0')
- import gtk
- gtk.init_check()
+ import gi
+ gi.require_version('Gtk', '3.0')
+ gi.require_version('PangoCairo', '1.0')
+
+ from gi.repository import Gtk
+ Gtk.init_check()
warnings.filterwarnings("always")
except Exception as err:
die(err, "Failed to initialize GTK. If you are running over ssh, "
diff --git a/grc/core/Block.py b/grc/core/Block.py
index 8a683a2b6b..9fff5afcb7 100644
--- a/grc/core/Block.py
+++ b/grc/core/Block.py
@@ -17,12 +17,16 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+from __future__ import absolute_import
+
import collections
import itertools
+from six.moves import map, range
+
from Cheetah.Template import Template
-from .utils import epy_block_io, odict
+from .utils import epy_block_io
from . Constants import (
BLOCK_FLAG_NEED_QT_GUI, BLOCK_FLAG_NEED_WX_GUI,
ADVANCED_PARAM_TAB, DEFAULT_PARAM_TAB,
@@ -59,184 +63,111 @@ class Block(Element):
Returns:
block a new block
"""
- # Grab the data
- self._doc = (n.find('doc') or '').strip('\n').replace('\\\n', '')
- self._imports = map(lambda i: i.strip(), n.findall('import'))
- self._make = n.find('make')
- self._var_make = n.find('var_make')
- self._checks = n.findall('check')
- self._callbacks = n.findall('callback')
- self._bus_structure_source = n.find('bus_structure_source') or ''
- self._bus_structure_sink = n.find('bus_structure_sink') or ''
- self.port_counters = [itertools.count(), itertools.count()]
+ Element.__init__(self, parent=flow_graph)
+
+ self._name = n['name']
+ self._key = n['key']
+ self.category = [cat.strip() for cat in n.get('category', '').split('/') if cat.strip()]
+ self._flags = n.get('flags', '')
+ self._doc = n.get('doc', '').strip('\n').replace('\\\n', '')
- # Build the block
- Element.__init__(self, flow_graph)
-
- # Grab the data
- params = n.findall('param')
- sources = n.findall('source')
- sinks = n.findall('sink')
- self._name = n.find('name')
- self._key = n.find('key')
- category = (n.find('category') or '').split('/')
- self.category = [cat.strip() for cat in category if cat.strip()]
- self._flags = n.find('flags') or ''
# Backwards compatibility
- if n.find('throttle') and BLOCK_FLAG_THROTTLE not in self._flags:
+ if n.get('throttle') and BLOCK_FLAG_THROTTLE not in self._flags:
self._flags += BLOCK_FLAG_THROTTLE
- self._grc_source = n.find('grc_source') or ''
- self._block_wrapper_path = n.find('block_wrapper_path')
- self._bussify_sink = n.find('bus_sink')
- self._bussify_source = n.find('bus_source')
- self._var_value = n.find('var_value') or '$value'
+
+ self._imports = [i.strip() for i in n.get('import', [])]
+ self._make = n.get('make')
+ self._var_make = n.get('var_make')
+ self._var_value = n.get('var_value', '$value')
+ self._checks = n.get('check', [])
+ self._callbacks = n.get('callback', [])
+
+ self._grc_source = n.get('grc_source', '')
+ self._block_wrapper_path = n.get('block_wrapper_path')
+
+ params_n = n.get('param', [])
+ sources_n = n.get('source', [])
+ sinks_n = n.get('sink', [])
# Get list of param tabs
- n_tabs = n.find('param_tab_order') or None
- self._param_tab_labels = n_tabs.findall('tab') if n_tabs is not None else [DEFAULT_PARAM_TAB]
-
- # Create the param objects
- self._params = list()
-
- # Add the id param
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({
- 'name': 'ID',
- 'key': 'id',
- 'type': 'id',
- })
- ))
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({
- 'name': 'Enabled',
- 'key': '_enabled',
- 'type': 'raw',
- 'value': 'True',
- 'hide': 'all',
- })
- ))
- for param in itertools.imap(lambda n: self.get_parent().get_parent().Param(block=self, n=n), params):
- key = param.get_key()
- # Test against repeated keys
- if key in self.get_param_keys():
- raise Exception('Key "{}" already exists in params'.format(key))
- # Store the param
- self.get_params().append(param)
- # Create the source objects
- self._sources = list()
- for source in map(lambda n: self.get_parent().get_parent().Port(block=self, n=n, dir='source'), sources):
- key = source.get_key()
- # Test against repeated keys
- if key in self.get_source_keys():
- raise Exception('Key "{}" already exists in sources'.format(key))
- # Store the port
- self.get_sources().append(source)
- self.back_ofthe_bus(self.get_sources())
- # Create the sink objects
- self._sinks = list()
- for sink in map(lambda n: self.get_parent().get_parent().Port(block=self, n=n, dir='sink'), sinks):
- key = sink.get_key()
- # Test against repeated keys
- if key in self.get_sink_keys():
- raise Exception('Key "{}" already exists in sinks'.format(key))
- # Store the port
- self.get_sinks().append(sink)
- self.back_ofthe_bus(self.get_sinks())
- self.current_bus_structure = {'source': '', 'sink': ''}
+ self._param_tab_labels = n.get('param_tab_order', {}).get('tab') or [DEFAULT_PARAM_TAB]
+ self._params = []
+ self._init_params(
+ params_n=params_n,
+ has_sinks=len(sinks_n),
+ has_sources=len(sources_n)
+ )
+
+ self.port_counters = [itertools.count(), itertools.count()]
+ self._sources = self._init_ports(sources_n, direction='source')
+ self._sinks = self._init_ports(sinks_n, direction='sink')
+
+ self._epy_source_hash = -1 # for epy blocks
+ self._epy_reload_error = None
+
+ self._init_bus_ports(n)
+
+ def _add_param(self, key, name, value='', type='raw', **kwargs):
+ n = {'key': key, 'name': name, 'value': value, 'type': type}
+ n.update(kwargs)
+ param = self.parent_platform.Param(block=self, n=n)
+ self._params.append(param)
+
+ def _init_params(self, params_n, has_sources, has_sinks):
+ self._add_param(key='id', name='ID', type='id')
+ self._add_param(key='_enabled', name='Enabled', value='True', type='raw', hide='all')
# Virtual source/sink and pad source/sink blocks are
# indistinguishable from normal GR blocks. Make explicit
# checks for them here since they have no work function or
# buffers to manage.
- self.is_virtual_or_pad = self._key in (
+ self.is_virtual_or_pad = is_virtual_or_pad = self._key in (
"virtual_source", "virtual_sink", "pad_source", "pad_sink")
- self.is_variable = self._key.startswith('variable')
+ self.is_variable = is_variable = self._key.startswith('variable')
self.is_import = (self._key == 'import')
# Disable blocks that are virtual/pads or variables
if self.is_virtual_or_pad or self.is_variable:
self._flags += BLOCK_FLAG_DISABLE_BYPASS
- if not (self.is_virtual_or_pad or self.is_variable or self._key == 'options'):
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({'name': 'Block Alias',
- 'key': 'alias',
- 'type': 'string',
- 'hide': 'part',
- 'tab': ADVANCED_PARAM_TAB
- })
- ))
-
- if (len(sources) or len(sinks)) and not self.is_virtual_or_pad:
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({'name': 'Core Affinity',
- 'key': 'affinity',
- 'type': 'int_vector',
- 'hide': 'part',
- 'tab': ADVANCED_PARAM_TAB
- })
- ))
- if len(sources) and not self.is_virtual_or_pad:
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({'name': 'Min Output Buffer',
- 'key': 'minoutbuf',
- 'type': 'int',
- 'hide': 'part',
- 'value': '0',
- 'tab': ADVANCED_PARAM_TAB
- })
- ))
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({'name': 'Max Output Buffer',
- 'key': 'maxoutbuf',
- 'type': 'int',
- 'hide': 'part',
- 'value': '0',
- 'tab': ADVANCED_PARAM_TAB
- })
- ))
-
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({'name': 'Comment',
- 'key': 'comment',
- 'type': '_multiline',
- 'hide': 'part',
- 'value': '',
- 'tab': ADVANCED_PARAM_TAB
- })
- ))
+ if not (is_virtual_or_pad or is_variable or self._key == 'options'):
+ self._add_param(key='alias', name='Block Alias', type='string',
+ hide='part', tab=ADVANCED_PARAM_TAB)
- self._epy_source_hash = -1 # for epy blocks
- self._epy_reload_error = None
+ if not is_virtual_or_pad and (has_sources or has_sinks):
+ self._add_param(key='affinity', name='Core Affinity', type='int_vector',
+ hide='part', tab=ADVANCED_PARAM_TAB)
- if self._bussify_sink:
- self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
- if self._bussify_source:
- self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
+ if not is_virtual_or_pad and has_sources:
+ self._add_param(key='minoutbuf', name='Min Output Buffer', type='int',
+ hide='part', value='0', tab=ADVANCED_PARAM_TAB)
+ self._add_param(key='maxoutbuf', name='Max Output Buffer', type='int',
+ hide='part', value='0', tab=ADVANCED_PARAM_TAB)
- def get_bus_structure(self, direction):
- if direction == 'source':
- bus_structure = self._bus_structure_source
- else:
- bus_structure = self._bus_structure_sink
-
- bus_structure = self.resolve_dependencies(bus_structure)
-
- if not bus_structure:
- return '' # TODO: Don't like empty strings. should change this to None eventually
+ param_keys = set(param.get_key() for param in self._params)
+ for param_n in params_n:
+ param = self.parent_platform.Param(block=self, n=param_n)
+ key = param.get_key()
+ if key in param_keys:
+ raise Exception('Key "{}" already exists in params'.format(key))
+ param_keys.add(key)
+ self.get_params().append(param)
- try:
- clean_bus_structure = self.get_parent().evaluate(bus_structure)
- return clean_bus_structure
- except:
- return ''
+ self._add_param(key='comment', name='Comment', type='_multiline', hide='part',
+ value='', tab=ADVANCED_PARAM_TAB)
+
+ def _init_ports(self, ports_n, direction):
+ port_cls = self.parent_platform.Port
+ ports = []
+ port_keys = set()
+ for port_n in ports_n:
+ port = port_cls(block=self, n=port_n, dir=direction)
+ key = port.get_key()
+ if key in port_keys:
+ raise Exception('Key "{}" already exists in {}'.format(key, direction))
+ port_keys.add(key)
+ ports.append(port)
+ return ports
def validate(self):
"""
@@ -249,7 +180,7 @@ class Block(Element):
for check in self._checks:
check_res = self.resolve_dependencies(check)
try:
- if not self.get_parent().evaluate(check_res):
+ if not self.parent.evaluate(check_res):
self.add_error_message('Check "{}" failed.'.format(check))
except:
self.add_error_message('Check "{}" did not evaluate.'.format(check))
@@ -259,12 +190,12 @@ class Block(Element):
value = self._var_value
try:
value = self.get_var_value()
- self.get_parent().evaluate(value)
+ self.parent.evaluate(value)
except Exception as err:
self.add_error_message('Value "{}" cannot be evaluated:\n{}'.format(value, err))
# check if this is a GUI block and matches the selected generate option
- current_generate_option = self.get_parent().get_option('generate_options')
+ current_generate_option = self.parent.get_option('generate_options')
def check_generate_mode(label, flag, valid_options):
block_requires_mode = (
@@ -295,14 +226,14 @@ class Block(Element):
num_ports = 1 + len(master_port.get_clones())
if master_port.get_hide():
for connection in master_port.get_connections():
- self.get_parent().remove_element(connection)
+ self.parent.remove_element(connection)
if not nports and num_ports == 1: # Not a master port and no left-over clones
continue
# Remove excess cloned ports
for port in master_port.get_clones()[nports-1:]:
# Remove excess connections
for connection in port.get_connections():
- self.get_parent().remove_element(connection)
+ self.parent.remove_element(connection)
master_port.remove_clone(port)
ports.remove(port)
# Add more cloned ports
@@ -313,42 +244,13 @@ class Block(Element):
self.back_ofthe_bus(ports)
# Renumber non-message/message ports
domain_specific_port_index = collections.defaultdict(int)
- for port in filter(lambda p: p.get_key().isdigit(), ports):
+ for port in [p for p in ports if p.get_key().isdigit()]:
domain = port.get_domain()
port._key = str(domain_specific_port_index[domain])
domain_specific_port_index[domain] += 1
- def port_controller_modify(self, direction):
- """
- Change the port controller.
-
- Args:
- direction: +1 or -1
-
- Returns:
- true for change
- """
- changed = False
- # Concat the nports string from the private nports settings of all ports
- nports_str = ' '.join([port._nports for port in self.get_ports()])
- # Modify all params whose keys appear in the nports string
- for param in self.get_params():
- if param.is_enum() or param.get_key() not in nports_str:
- continue
- # Try to increment the port controller by direction
- try:
- value = param.get_evaluated()
- value = value + direction
- if 0 < value:
- param.set_value(value)
- changed = True
- except:
- pass
- return changed
-
def get_doc(self):
- platform = self.get_parent().get_parent()
- documentation = platform.block_docstrings.get(self._key, {})
+ documentation = self.parent_platform.block_docstrings.get(self._key, {})
from_xml = self._doc.strip()
if from_xml:
documentation[''] = from_xml
@@ -366,7 +268,8 @@ class Block(Element):
"""
if raw:
return self._imports
- return filter(lambda i: i, sum(map(lambda i: self.resolve_dependencies(i).split('\n'), self._imports), []))
+ return [i for i in sum((self.resolve_dependencies(i).split('\n')
+ for i in self._imports), []) if i]
def get_make(self, raw=False):
if raw:
@@ -391,7 +294,7 @@ class Block(Element):
if 'self.' in callback:
return callback
return 'self.{}.{}'.format(self.get_id(), callback)
- return map(make_callback, self._callbacks)
+ return [make_callback(c) for c in self._callbacks]
def is_virtual_sink(self):
return self.get_key() == 'virtual_sink'
@@ -404,8 +307,8 @@ class Block(Element):
###########################################################################
def rewrite_epy_block(self):
- flowgraph = self.get_parent()
- platform = flowgraph.get_parent()
+ flowgraph = self.parent_flowgraph
+ platform = self.parent_block
param_blk = self.get_param('_io_cache')
param_src = self.get_param('_source_code')
@@ -452,7 +355,7 @@ class Block(Element):
param.set_default(value)
except KeyError: # need to make a new param
name = key.replace('_', ' ').title()
- n = odict(dict(name=name, key=key, type='raw', value=value))
+ n = dict(name=name, key=key, type='raw', value=value)
param = platform.Param(block=self, n=n)
setattr(param, '__epy_param__', True)
self._params.append(param)
@@ -472,7 +375,7 @@ class Block(Element):
ports_to_remove.remove(port_current)
port, port_current = port_current, next(iter_ports, None)
else:
- n = odict(dict(name=label + str(key), type=port_type, key=key))
+ n = dict(name=label + str(key), type=port_type, key=key)
if port_type == 'message':
n['name'] = key
n['optional'] = '1'
@@ -490,13 +393,6 @@ class Block(Element):
update_ports('out', self.get_sources(), blk_io.sources, 'source')
self.rewrite()
- def back_ofthe_bus(self, portlist):
- portlist.sort(key=lambda p: p._type == 'bus')
-
- def filter_bus_port(self, ports):
- buslist = [p for p in ports if p._type == 'bus']
- return buslist or ports
-
# Main functions to get and set the block state
# Also kept get_enabled and set_enabled to keep compatibility
def get_state(self):
@@ -651,9 +547,6 @@ class Block(Element):
##############################################
# Access Sinks
##############################################
- def get_sink_keys(self):
- return _get_keys(self._sinks)
-
def get_sink(self, key):
return _get_elem(self._sinks, key)
@@ -666,9 +559,6 @@ class Block(Element):
##############################################
# Access Sources
##############################################
- def get_source_keys(self):
- return _get_keys(self._sources)
-
def get_source(self, key):
return _get_elem(self._sources, key)
@@ -681,6 +571,10 @@ class Block(Element):
def get_connections(self):
return sum([port.get_connections() for port in self.get_ports()], [])
+ ##############################################
+ # Resolve
+ ##############################################
+
def resolve_dependencies(self, tmpl):
"""
Resolve a paramater dependency with cheetah templates.
@@ -716,10 +610,10 @@ class Block(Element):
"""
changed = False
type_param = None
- for param in filter(lambda p: p.is_enum(), self.get_params()):
+ for param in [p for p in self.get_params() if p.is_enum()]:
children = self.get_ports() + self.get_params()
# Priority to the type controller
- if param.get_key() in ' '.join(map(lambda p: p._type, children)): type_param = param
+ if param.get_key() in ' '.join([p._type for p in children]): type_param = param
# Use param if type param is unset
if not type_param:
type_param = param
@@ -735,6 +629,128 @@ class Block(Element):
pass
return changed
+ def port_controller_modify(self, direction):
+ """
+ Change the port controller.
+
+ Args:
+ direction: +1 or -1
+
+ Returns:
+ true for change
+ """
+ changed = False
+ # Concat the nports string from the private nports settings of all ports
+ nports_str = ' '.join([port._nports for port in self.get_ports()])
+ # Modify all params whose keys appear in the nports string
+ for param in self.get_params():
+ if param.is_enum() or param.get_key() not in nports_str:
+ continue
+ # Try to increment the port controller by direction
+ try:
+ value = param.get_evaluated()
+ value = value + direction
+ if 0 < value:
+ param.set_value(value)
+ changed = True
+ except:
+ pass
+ return changed
+
+ ##############################################
+ # Import/Export Methods
+ ##############################################
+ def export_data(self):
+ """
+ Export this block's params to nested data.
+
+ Returns:
+ a nested data odict
+ """
+ n = collections.OrderedDict()
+ n['key'] = self.get_key()
+ n['param'] = [p.export_data() for p in sorted(self.get_params(), key=str)]
+ if 'bus' in [a.get_type() for a in self.get_sinks()]:
+ n['bus_sink'] = str(1)
+ if 'bus' in [a.get_type() for a in self.get_sources()]:
+ n['bus_source'] = str(1)
+ return n
+
+ def import_data(self, n):
+ """
+ Import this block's params from nested data.
+ Any param keys that do not exist will be ignored.
+ Since params can be dynamically created based another param,
+ call rewrite, and repeat the load until the params stick.
+ This call to rewrite will also create any dynamic ports
+ that are needed for the connections creation phase.
+
+ Args:
+ n: the nested data odict
+ """
+ params_n = n.get('param', [])
+ params = dict((param.get_key(), param) for param in self._params)
+
+ def get_hash():
+ return hash(tuple(map(hash, self._params)))
+
+ my_hash = 0
+ while get_hash() != my_hash:
+ for param_n in params_n:
+ key = param_n['key']
+ value = param_n['value']
+ try:
+ params[key].set_value(value)
+ except KeyError:
+ continue
+ # Store hash and call rewrite
+ my_hash = get_hash()
+ self.rewrite()
+
+ self._import_bus_stuff(n)
+
+ ##############################################
+ # Bus ports stuff
+ ##############################################
+
+ def get_bus_structure(self, direction):
+ if direction == 'source':
+ bus_structure = self._bus_structure_source
+ else:
+ bus_structure = self._bus_structure_sink
+
+ bus_structure = self.resolve_dependencies(bus_structure)
+
+ if not bus_structure:
+ return '' # TODO: Don't like empty strings. should change this to None eventually
+
+ try:
+ clean_bus_structure = self.parent.evaluate(bus_structure)
+ return clean_bus_structure
+ except:
+ return ''
+
+ def back_ofthe_bus(self, portlist):
+ portlist.sort(key=lambda p: p._type == 'bus')
+
+ def filter_bus_port(self, ports):
+ buslist = [p for p in ports if p._type == 'bus']
+ return buslist or ports
+
+ def _import_bus_stuff(self, n):
+ bussinks = n.get('bus_sink', [])
+ if len(bussinks) > 0 and not self._bussify_sink:
+ self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
+ elif len(bussinks) > 0:
+ self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
+ self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
+ bussrcs = n.get('bus_source', [])
+ if len(bussrcs) > 0 and not self._bussify_source:
+ self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
+ elif len(bussrcs) > 0:
+ self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
+ self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
+
def form_bus_structure(self, direc):
if direc == 'source':
get_p = self.get_sources
@@ -745,12 +761,12 @@ class Block(Element):
get_p_gui = self.get_sinks_gui
bus_structure = self.get_bus_structure('sink')
- struct = [range(len(get_p()))]
- if True in map(lambda a: isinstance(a.get_nports(), int), get_p()):
+ struct = [list(range(len(get_p())))]
+ if True in [isinstance(a.get_nports(), int) for a in get_p()]:
structlet = []
last = 0
for j in [i.get_nports() for i in get_p() if isinstance(i.get_nports(), int)]:
- structlet.extend(map(lambda a: a+last, range(j)))
+ structlet.extend([a+last for a in range(j)])
last = structlet[-1] + 1
struct = [structlet]
if bus_structure:
@@ -764,17 +780,15 @@ class Block(Element):
if direc == 'source':
get_p = self.get_sources
get_p_gui = self.get_sources_gui
- bus_structure = self.get_bus_structure('source')
else:
get_p = self.get_sinks
get_p_gui = self.get_sinks_gui
- bus_structure = self.get_bus_structure('sink')
for elt in get_p():
for connect in elt.get_connections():
- self.get_parent().remove_element(connect)
+ self.parent.remove_element(connect)
- if ('bus' not in map(lambda a: a.get_type(), get_p())) and len(get_p()) > 0:
+ if ('bus' not in [a.get_type() for a in get_p()]) and len(get_p()) > 0:
struct = self.form_bus_structure(direc)
self.current_bus_structure[direc] = struct
if get_p()[0].get_nports():
@@ -782,69 +796,23 @@ class Block(Element):
for i in range(len(struct)):
n['key'] = str(len(get_p()))
- n = odict(n)
- port = self.get_parent().get_parent().Port(block=self, n=n, dir=direc)
+ n = dict(n)
+ port = self.parent.parent.Port(block=self, n=n, dir=direc)
get_p().append(port)
- elif 'bus' in map(lambda a: a.get_type(), get_p()):
+ elif 'bus' in [a.get_type() for a in get_p()]:
for elt in get_p_gui():
get_p().remove(elt)
self.current_bus_structure[direc] = ''
- ##############################################
- # Import/Export Methods
- ##############################################
- def export_data(self):
- """
- Export this block's params to nested data.
-
- Returns:
- a nested data odict
- """
- n = odict()
- n['key'] = self.get_key()
- n['param'] = map(lambda p: p.export_data(), sorted(self.get_params(), key=str))
- if 'bus' in map(lambda a: a.get_type(), self.get_sinks()):
- n['bus_sink'] = str(1)
- if 'bus' in map(lambda a: a.get_type(), self.get_sources()):
- n['bus_source'] = str(1)
- return n
-
- def get_hash(self):
- return hash(tuple(map(hash, self.get_params())))
-
- def import_data(self, n):
- """
- Import this block's params from nested data.
- Any param keys that do not exist will be ignored.
- Since params can be dynamically created based another param,
- call rewrite, and repeat the load until the params stick.
- This call to rewrite will also create any dynamic ports
- that are needed for the connections creation phase.
-
- Args:
- n: the nested data odict
- """
- my_hash = 0
- while self.get_hash() != my_hash:
- params_n = n.findall('param')
- for param_n in params_n:
- key = param_n.find('key')
- value = param_n.find('value')
- # The key must exist in this block's params
- if key in self.get_param_keys():
- self.get_param(key).set_value(value)
- # Store hash and call rewrite
- my_hash = self.get_hash()
- self.rewrite()
- bussinks = n.findall('bus_sink')
- if len(bussinks) > 0 and not self._bussify_sink:
- self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
- elif len(bussinks) > 0:
- self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
+ def _init_bus_ports(self, n):
+ self.back_ofthe_bus(self._sources)
+ self.back_ofthe_bus(self._sinks)
+ self.current_bus_structure = {'source': '', 'sink': ''}
+ self._bus_structure_source = n.get('bus_structure_source', '')
+ self._bus_structure_sink = n.get('bus_structure_sink', '')
+ self._bussify_sink = n.get('bus_sink')
+ self._bussify_source = n.get('bus_source')
+ if self._bussify_sink:
self.bussify({'name': 'bus', 'type': 'bus'}, 'sink')
- bussrcs = n.findall('bus_source')
- if len(bussrcs) > 0 and not self._bussify_source:
- self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
- elif len(bussrcs) > 0:
- self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
+ if self._bussify_source:
self.bussify({'name': 'bus', 'type': 'bus'}, 'source')
diff --git a/grc/core/Config.py b/grc/core/Config.py
index ac38d9978c..400d5d365f 100644
--- a/grc/core/Config.py
+++ b/grc/core/Config.py
@@ -17,6 +17,7 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+from __future__ import absolute_import
import os
from os.path import expanduser, normpath, expandvars, exists
diff --git a/grc/core/Connection.py b/grc/core/Connection.py
index 3aa32ef183..52cba4257c 100644
--- a/grc/core/Connection.py
+++ b/grc/core/Connection.py
@@ -17,9 +17,14 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+from __future__ import absolute_import
+
+import collections
+
+from six.moves import range
+
from . import Constants
-from .Element import Element
-from .utils import odict
+from .Element import Element, lazy_property
class Connection(Element):
@@ -40,6 +45,21 @@ class Connection(Element):
a new connection
"""
Element.__init__(self, flow_graph)
+
+ source, sink = self._get_sink_source(porta, portb)
+
+ self.source_port = source
+ self.sink_port = sink
+
+ # Ensure that this connection (source -> sink) is unique
+ for connection in flow_graph.connections:
+ if connection.source_port is source and connection.sink_port is sink:
+ raise LookupError('This connection between source and sink is not unique.')
+
+ self._make_bus_connect()
+
+ @staticmethod
+ def _get_sink_source(porta, portb):
source = sink = None
# Separate the source and sink
for port in (porta, portb):
@@ -51,42 +71,18 @@ class Connection(Element):
raise ValueError('Connection could not isolate source')
if not sink:
raise ValueError('Connection could not isolate sink')
- busses = len(filter(lambda a: a.get_type() == 'bus', [source, sink])) % 2
- if not busses == 0:
- raise ValueError('busses must get with busses')
-
- if not len(source.get_associated_ports()) == len(sink.get_associated_ports()):
- raise ValueError('port connections must have same cardinality')
- # Ensure that this connection (source -> sink) is unique
- for connection in flow_graph.connections:
- if connection.get_source() is source and connection.get_sink() is sink:
- raise LookupError('This connection between source and sink is not unique.')
- self._source = source
- self._sink = sink
- if source.get_type() == 'bus':
-
- sources = source.get_associated_ports()
- sinks = sink.get_associated_ports()
-
- for i in range(len(sources)):
- try:
- flow_graph.connect(sources[i], sinks[i])
- except:
- pass
+ return source, sink
def __str__(self):
return 'Connection (\n\t{}\n\t\t{}\n\t{}\n\t\t{}\n)'.format(
- self.get_source().get_parent(),
- self.get_source(),
- self.get_sink().get_parent(),
- self.get_sink(),
+ self.source_block, self.source_port, self.sink_block, self.sink_port,
)
def is_msg(self):
- return self.get_source().get_type() == self.get_sink().get_type() == 'msg'
+ return self.source_port.get_type() == self.sink_port.get_type() == 'msg'
def is_bus(self):
- return self.get_source().get_type() == self.get_sink().get_type() == 'bus'
+ return self.source_port.get_type() == self.sink_port.get_type() == 'bus'
def validate(self):
"""
@@ -98,19 +94,21 @@ class Connection(Element):
The ports must match in type.
"""
Element.validate(self)
- platform = self.get_parent().get_parent()
- source_domain = self.get_source().get_domain()
- sink_domain = self.get_sink().get_domain()
+ platform = self.parent_platform
+
+ source_domain = self.source_port.get_domain()
+ sink_domain = self.sink_port.get_domain()
+
if (source_domain, sink_domain) not in platform.connection_templates:
self.add_error_message('No connection known for domains "{}", "{}"'.format(
- source_domain, sink_domain))
+ source_domain, sink_domain))
too_many_other_sinks = (
not platform.domains.get(source_domain, []).get('multiple_sinks', False) and
- len(self.get_source().get_enabled_connections()) > 1
+ len(self.source_port.get_enabled_connections()) > 1
)
too_many_other_sources = (
not platform.domains.get(sink_domain, []).get('multiple_sources', False) and
- len(self.get_sink().get_enabled_connections()) > 1
+ len(self.sink_port.get_enabled_connections()) > 1
)
if too_many_other_sinks:
self.add_error_message(
@@ -119,8 +117,8 @@ class Connection(Element):
self.add_error_message(
'Domain "{}" can have only one upstream block'.format(sink_domain))
- source_size = Constants.TYPE_TO_SIZEOF[self.get_source().get_type()] * self.get_source().get_vlen()
- sink_size = Constants.TYPE_TO_SIZEOF[self.get_sink().get_type()] * self.get_sink().get_vlen()
+ source_size = Constants.TYPE_TO_SIZEOF[self.source_port.get_type()] * self.source_port.get_vlen()
+ sink_size = Constants.TYPE_TO_SIZEOF[self.sink_port.get_type()] * self.sink_port.get_vlen()
if source_size != sink_size:
self.add_error_message('Source IO size "{}" does not match sink IO size "{}".'.format(source_size, sink_size))
@@ -131,17 +129,15 @@ class Connection(Element):
Returns:
true if source and sink blocks are enabled
"""
- return self.get_source().get_parent().get_enabled() and \
- self.get_sink().get_parent().get_enabled()
+ return self.source_block.get_enabled() and self.sink_block.get_enabled()
- #############################
- # Access Ports
- #############################
- def get_sink(self):
- return self._sink
+ @lazy_property
+ def source_block(self):
+ return self.source_port.parent_block
- def get_source(self):
- return self._source
+ @lazy_property
+ def sink_block(self):
+ return self.sink_port.parent_block
##############################################
# Import/Export Methods
@@ -153,9 +149,28 @@ class Connection(Element):
Returns:
a nested data odict
"""
- n = odict()
- n['source_block_id'] = self.get_source().get_parent().get_id()
- n['sink_block_id'] = self.get_sink().get_parent().get_id()
- n['source_key'] = self.get_source().get_key()
- n['sink_key'] = self.get_sink().get_key()
+ n = collections.OrderedDict()
+ n['source_block_id'] = self.source_block.get_id()
+ n['sink_block_id'] = self.sink_block.get_id()
+ n['source_key'] = self.source_port.get_key()
+ n['sink_key'] = self.sink_port.get_key()
return n
+
+ def _make_bus_connect(self):
+ source, sink = self.source_port, self.sink_port
+
+ if (source.get_type() == 'bus') != (sink.get_type() == 'bus'):
+ raise ValueError('busses must get with busses')
+
+ if not len(source.get_associated_ports()) == len(sink.get_associated_ports()):
+ raise ValueError('port connections must have same cardinality')
+
+ if source.get_type() == 'bus':
+ sources = source.get_associated_ports()
+ sinks = sink.get_associated_ports()
+
+ for i in range(len(sources)):
+ try:
+ self.parent_flowgraph.connect(sources[i], sinks[i])
+ except:
+ pass
diff --git a/grc/core/Constants.py b/grc/core/Constants.py
index eeb1d7f848..992d5e7d83 100644
--- a/grc/core/Constants.py
+++ b/grc/core/Constants.py
@@ -17,10 +17,14 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+from __future__ import absolute_import
+
import os
-import numpy
import stat
+import numpy
+import six
+
# Data files
DATA_DIR = os.path.dirname(__file__)
FLOW_GRAPH_DTD = os.path.join(DATA_DIR, 'flow_graph.dtd')
@@ -64,15 +68,15 @@ HIER_BLOCK_FILE_MODE = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP
VECTOR_TYPES = (tuple, list, set, numpy.ndarray)
COMPLEX_TYPES = [complex, numpy.complex, numpy.complex64, numpy.complex128]
REAL_TYPES = [float, numpy.float, numpy.float32, numpy.float64]
-INT_TYPES = [int, long, numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.uint64,
+INT_TYPES = [int, numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.uint64,
numpy.uint, numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64]
# Cast to tuple for isinstance, concat subtypes
COMPLEX_TYPES = tuple(COMPLEX_TYPES + REAL_TYPES + INT_TYPES)
REAL_TYPES = tuple(REAL_TYPES + INT_TYPES)
INT_TYPES = tuple(INT_TYPES)
-# Updating colors. Using the standard color pallette from:
-# http://www.google.com/design/spec/style/color.html#color-color-palette
+# Updating colors. Using the standard color palette from:
+# http://www.google.com/design/spec/style/color.html#color-color-palette
# Most are based on the main, primary color standard. Some are within
# that color's spectrum when it was deemed necessary.
GRC_COLOR_BROWN = '#795548'
@@ -130,21 +134,6 @@ for name, key, sizeof, color in CORE_TYPES:
TYPE_TO_COLOR[key] = color
TYPE_TO_SIZEOF[key] = sizeof
-for key, (sizeof, color) in ALIAS_TYPES.iteritems():
+for key, (sizeof, color) in six.iteritems(ALIAS_TYPES):
TYPE_TO_COLOR[key] = color
TYPE_TO_SIZEOF[key] = sizeof
-
-# Coloring
-COMPLEX_COLOR_SPEC = '#3399FF'
-FLOAT_COLOR_SPEC = '#FF8C69'
-INT_COLOR_SPEC = '#00FF99'
-SHORT_COLOR_SPEC = '#FFFF66'
-BYTE_COLOR_SPEC = '#FF66FF'
-COMPLEX_VECTOR_COLOR_SPEC = '#3399AA'
-FLOAT_VECTOR_COLOR_SPEC = '#CC8C69'
-INT_VECTOR_COLOR_SPEC = '#00CC99'
-SHORT_VECTOR_COLOR_SPEC = '#CCCC33'
-BYTE_VECTOR_COLOR_SPEC = '#CC66CC'
-ID_COLOR_SPEC = '#DDDDDD'
-WILDCARD_COLOR_SPEC = '#FFFFFF'
-MSG_COLOR_SPEC = '#777777'
diff --git a/grc/core/Element.py b/grc/core/Element.py
index 67c36e12b4..f07bb113e1 100644
--- a/grc/core/Element.py
+++ b/grc/core/Element.py
@@ -1,28 +1,44 @@
-"""
-Copyright 2008, 2009, 2015 Free Software Foundation, Inc.
-This file is part of GNU Radio
-
-GNU Radio Companion 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 2
-of the License, or (at your option) any later version.
-
-GNU Radio Companion is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-"""
+# Copyright 2008, 2009, 2015, 2016 Free Software Foundation, Inc.
+# This file is part of GNU Radio
+#
+# GNU Radio Companion 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 2
+# of the License, or (at your option) any later version.
+#
+# GNU Radio Companion is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+import weakref
+import functools
+
+
+class lazy_property(object):
+
+ def __init__(self, func):
+ self.func = func
+ functools.update_wrapper(self, func)
+
+ def __get__(self, instance, cls):
+ if instance is None:
+ return self
+ value = self.func(instance)
+ weak_value = weakref.proxy(value) if not weakref.ProxyType else value
+ setattr(instance, self.func.__name__, weak_value)
+ return weak_value
class Element(object):
def __init__(self, parent=None):
- self._parent = parent
- self._error_messages = list()
+ self._parent = weakref.ref(parent) if parent else lambda: None
+ self._error_messages = []
##################################################
# Element Validation API
@@ -33,6 +49,7 @@ class Element(object):
Call this base method before adding error messages in the subclass.
"""
del self._error_messages[:]
+
for child in self.get_children():
child.validate()
@@ -64,7 +81,9 @@ class Element(object):
a list of error message strings
"""
error_messages = list(self._error_messages) # Make a copy
- for child in filter(lambda c: c.get_enabled() and not c.get_bypassed(), self.get_children()):
+ for child in self.get_children():
+ if not child.get_enabled() or child.get_bypassed():
+ continue
for msg in child.get_error_messages():
error_messages.append("{}:\n\t{}".format(child, msg.replace("\n", "\n\t")))
return error_messages
@@ -86,8 +105,39 @@ class Element(object):
##############################################
# Tree-like API
##############################################
- def get_parent(self):
- return self._parent
+ @property
+ def parent(self):
+ return self._parent()
+
+ def get_parent_by_type(self, cls):
+ parent = self.parent
+ if parent is None:
+ return None
+ elif isinstance(parent, cls):
+ return parent
+ else:
+ return parent.get_parent_by_type(cls)
+
+ @lazy_property
+ def parent_platform(self):
+ from .Platform import Platform
+ return self.get_parent_by_type(Platform)
+
+ @lazy_property
+ def parent_flowgraph(self):
+ from .FlowGraph import FlowGraph
+ return self.get_parent_by_type(FlowGraph)
+
+ @lazy_property
+ def parent_block(self):
+ from .Block import Block
+ return self.get_parent_by_type(Block)
+
+ def reset_parents_by_type(self):
+ """Reset all lazy properties"""
+ for name, obj in vars(Element):
+ if isinstance(obj, lazy_property):
+ delattr(self, name)
def get_children(self):
return list()
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index 949eecaa71..67e86f3e6e 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -15,17 +15,21 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+from __future__ import absolute_import, print_function
+
import imp
import time
-from itertools import ifilter, chain
+import re
+from itertools import chain
from operator import methodcaller, attrgetter
+import collections
-import re
+from six.moves import filter
from . import Messages
from .Constants import FLOW_GRAPH_FILE_FORMAT_VERSION
from .Element import Element
-from .utils import odict, expr_utils
+from .utils import expr_utils
_parameter_matcher = re.compile('^(parameter)$')
_monitors_searcher = re.compile('(ctrlport_monitor)')
@@ -49,26 +53,21 @@ class FlowGraph(Element):
Returns:
the flow graph object
"""
- Element.__init__(self, platform)
- self._elements = []
+ Element.__init__(self, parent=platform)
self._timestamp = time.ctime()
+ self._options_block = self.parent_platform.get_new_block(self, 'options')
- self.platform = platform # todo: make this a lazy prop
- self.blocks = []
+ self.blocks = [self._options_block]
self.connections = []
self._eval_cache = {}
self.namespace = {}
self.grc_file_path = ''
- self._options_block = self.new_block('options')
def __str__(self):
return 'FlowGraph - {}({})'.format(self.get_option('title'), self.get_option('id'))
- ##############################################
- # TODO: Move these to new generator package
- ##############################################
def get_imports(self):
"""
Get a set of all import statements in this flow graph namespace.
@@ -87,7 +86,7 @@ class FlowGraph(Element):
Returns:
a sorted list of variable blocks in order of dependency (indep -> dep)
"""
- variables = filter(attrgetter('is_variable'), self.iter_enabled_blocks())
+ variables = list(filter(attrgetter('is_variable'), self.iter_enabled_blocks()))
return expr_utils.sort_objects(variables, methodcaller('get_id'), methodcaller('get_var_make'))
def get_parameters(self):
@@ -97,15 +96,14 @@ class FlowGraph(Element):
Returns:
a list of parameterized variables
"""
- parameters = filter(lambda b: _parameter_matcher.match(b.get_key()), self.iter_enabled_blocks())
+ parameters = [b for b in self.iter_enabled_blocks() if _parameter_matcher.match(b.get_key())]
return parameters
def get_monitors(self):
"""
Get a list of all ControlPort monitors
"""
- monitors = filter(lambda b: _monitors_searcher.search(b.get_key()),
- self.iter_enabled_blocks())
+ monitors = [b for b in self.iter_enabled_blocks() if _monitors_searcher.search(b.get_key())]
return monitors
def get_python_modules(self):
@@ -115,7 +113,7 @@ class FlowGraph(Element):
yield block.get_id(), block.get_param('source_code').get_value()
def get_bussink(self):
- bussink = filter(lambda b: _bussink_searcher.search(b.get_key()), self.get_enabled_blocks())
+ bussink = [b for b in self.get_enabled_blocks() if _bussink_searcher.search(b.get_key())]
for i in bussink:
for j in i.get_params():
@@ -124,7 +122,7 @@ class FlowGraph(Element):
return False
def get_bussrc(self):
- bussrc = filter(lambda b: _bussrc_searcher.search(b.get_key()), self.get_enabled_blocks())
+ bussrc = [b for b in self.get_enabled_blocks() if _bussrc_searcher.search(b.get_key())]
for i in bussrc:
for j in i.get_params():
@@ -133,18 +131,18 @@ class FlowGraph(Element):
return False
def get_bus_structure_sink(self):
- bussink = filter(lambda b: _bus_struct_sink_searcher.search(b.get_key()), self.get_enabled_blocks())
+ bussink = [b for b in self.get_enabled_blocks() if _bus_struct_sink_searcher.search(b.get_key())]
return bussink
def get_bus_structure_src(self):
- bussrc = filter(lambda b: _bus_struct_src_searcher.search(b.get_key()), self.get_enabled_blocks())
+ bussrc = [b for b in self.get_enabled_blocks() if _bus_struct_src_searcher.search(b.get_key())]
return bussrc
def iter_enabled_blocks(self):
"""
Get an iterator of all blocks that are enabled and not bypassed.
"""
- return ifilter(methodcaller('get_enabled'), self.blocks)
+ return filter(methodcaller('get_enabled'), self.blocks)
def get_enabled_blocks(self):
"""
@@ -162,7 +160,7 @@ class FlowGraph(Element):
Returns:
a list of blocks
"""
- return filter(methodcaller('get_bypassed'), self.blocks)
+ return list(filter(methodcaller('get_bypassed'), self.blocks))
def get_enabled_connections(self):
"""
@@ -171,7 +169,7 @@ class FlowGraph(Element):
Returns:
a list of connections
"""
- return filter(methodcaller('get_enabled'), self.connections)
+ return list(filter(methodcaller('get_enabled'), self.connections))
def get_option(self, key):
"""
@@ -196,19 +194,6 @@ class FlowGraph(Element):
raise KeyError('No block with ID {!r}'.format(id))
def get_elements(self):
- """
- Get a list of all the elements.
- Always ensure that the options block is in the list (only once).
-
- Returns:
- the element list
- """
- options_block_count = self.blocks.count(self._options_block)
- if not options_block_count:
- self.blocks.append(self._options_block)
- for i in range(options_block_count-1):
- self.blocks.remove(self._options_block)
-
return self.blocks + self.connections
get_children = get_elements
@@ -217,7 +202,6 @@ class FlowGraph(Element):
"""
Flag the namespace to be renewed.
"""
-
self.renew_namespace()
for child in chain(self.blocks, self.connections):
child.rewrite()
@@ -229,14 +213,14 @@ class FlowGraph(Element):
# Load imports
for expr in self.get_imports():
try:
- exec expr in namespace
+ exec(expr, namespace)
except:
pass
for id, expr in self.get_python_modules():
try:
module = imp.new_module(id)
- exec expr in module.__dict__
+ exec(expr, module.__dict__)
namespace[id] = module
except:
pass
@@ -294,8 +278,10 @@ class FlowGraph(Element):
Returns:
the new block or None if not found
"""
+ if key == 'options':
+ return self._options_block
try:
- block = self.platform.get_new_block(self, key)
+ block = self.parent_platform.get_new_block(self, key)
self.blocks.append(block)
except KeyError:
block = None
@@ -314,7 +300,7 @@ class FlowGraph(Element):
the new connection
"""
- connection = self.platform.Connection(
+ connection = self.parent_platform.Connection(
flow_graph=self, porta=porta, portb=portb)
self.connections.append(connection)
return connection
@@ -326,22 +312,25 @@ class FlowGraph(Element):
If the element is a block, remove its connections.
If the element is a connection, just remove the connection.
"""
+ if element is self._options_block:
+ return
+
if element.is_port:
# Found a port, set to parent signal block
- element = element.get_parent()
+ element = element.parent
if element in self.blocks:
# Remove block, remove all involved connections
for port in element.get_ports():
- map(self.remove_element, port.get_connections())
+ for connection in port.get_connections():
+ self.remove_element(connection)
self.blocks.remove(element)
elif element in self.connections:
if element.is_bus():
- cons_list = []
- for i in map(lambda a: a.get_connections(), element.get_source().get_associated_ports()):
- cons_list.extend(i)
- map(self.remove_element, cons_list)
+ for port in element.source_port.get_associated_ports():
+ for connection in port.get_connections():
+ self.remove_element(connection)
self.connections.remove(element)
##############################################
@@ -362,20 +351,19 @@ class FlowGraph(Element):
str(b)
))
connections = sorted(self.connections, key=str)
- n = odict()
+ n = collections.OrderedDict()
n['timestamp'] = self._timestamp
n['block'] = [b.export_data() for b in blocks]
n['connection'] = [c.export_data() for c in connections]
- instructions = odict({
- 'created': '.'.join(self.get_parent().config.version_parts),
- 'format': FLOW_GRAPH_FILE_FORMAT_VERSION,
- })
- return odict({'flow_graph': n, '_instructions': instructions})
+ instructions = collections.OrderedDict()
+ instructions['created'] = '.'.join(self.parent.config.version_parts)
+ instructions['format'] = FLOW_GRAPH_FILE_FORMAT_VERSION
+ return {'flow_graph': n, '_instructions': instructions}
def import_data(self, n):
"""
Import blocks and connections into this flow graph.
- Clear this flowgraph of all previous blocks and connections.
+ Clear this flow graph of all previous blocks and connections.
Any blocks or connections in error will be ignored.
Args:
@@ -386,31 +374,31 @@ class FlowGraph(Element):
del self.connections[:]
# set file format
try:
- instructions = n.find('_instructions') or {}
+ instructions = n.get('_instructions', {})
file_format = int(instructions.get('format', '0')) or _guess_file_format_1(n)
except:
file_format = 0
- fg_n = n and n.find('flow_graph') or odict() # use blank data if none provided
- self._timestamp = fg_n.find('timestamp') or time.ctime()
+ fg_n = n and n.get('flow_graph', {}) # use blank data if none provided
+ self._timestamp = fg_n.get('timestamp', time.ctime())
# build the blocks
- self._options_block = self.new_block('options')
- for block_n in fg_n.findall('block'):
- key = block_n.find('key')
- block = self._options_block if key == 'options' else self.new_block(key)
+ self.blocks.append(self._options_block)
+ for block_n in fg_n.get('block', []):
+ key = block_n['key']
+ block = self.new_block(key)
if not block:
# we're before the initial fg update(), so no evaluated values!
# --> use raw value instead
path_param = self._options_block.get_param('hier_block_src_path')
- file_path = self.platform.find_file_in_paths(
+ file_path = self.parent_platform.find_file_in_paths(
filename=key + '.grc',
paths=path_param.get_value(),
cwd=self.grc_file_path
)
if file_path: # grc file found. load and get block
- self.platform.load_and_generate_flow_graph(file_path)
+ self.parent_platform.load_and_generate_flow_graph(file_path)
block = self.new_block(key) # can be None
if not block: # looks like this block key cannot be found
@@ -440,12 +428,12 @@ class FlowGraph(Element):
return port
errors = False
- for connection_n in fg_n.findall('connection'):
+ for connection_n in fg_n.get('connection', []):
# get the block ids and port keys
- source_block_id = connection_n.find('source_block_id')
- sink_block_id = connection_n.find('sink_block_id')
- source_key = connection_n.find('source_key')
- sink_key = connection_n.find('sink_key')
+ source_block_id = connection_n.get('source_block_id')
+ sink_block_id = connection_n.get('sink_block_id')
+ source_key = connection_n.get('source_key')
+ sink_key = connection_n.get('sink_key')
try:
source_block = self.get_block(source_block_id)
sink_block = self.get_block(sink_block_id)
@@ -484,31 +472,28 @@ class FlowGraph(Element):
get_p_gui = block.get_sinks_gui
bus_structure = block.form_bus_structure('sink')
- if 'bus' in map(lambda a: a.get_type(), get_p_gui()):
+ if 'bus' in [a.get_type() for a in get_p_gui()]:
if len(get_p_gui()) > len(bus_structure):
times = range(len(bus_structure), len(get_p_gui()))
for i in times:
for connect in get_p_gui()[-1].get_connections():
- block.get_parent().remove_element(connect)
+ block.parent.remove_element(connect)
get_p().remove(get_p_gui()[-1])
elif len(get_p_gui()) < len(bus_structure):
n = {'name': 'bus', 'type': 'bus'}
- if True in map(
- lambda a: isinstance(a.get_nports(), int),
- get_p()):
+ if any(isinstance(a.get_nports(), int) for a in get_p()):
n['nports'] = str(1)
times = range(len(get_p_gui()), len(bus_structure))
for i in times:
n['key'] = str(len(get_p()))
- n = odict(n)
- port = block.get_parent().get_parent().Port(
+ n = dict(n)
+ port = block.parent.parent.Port(
block=block, n=n, dir=direc)
get_p().append(port)
- if 'bus' in map(lambda a: a.get_type(),
- block.get_sources_gui()):
+ if 'bus' in [a.get_type() for a in block.get_sources_gui()]:
for i in range(len(block.get_sources_gui())):
if len(block.get_sources_gui()[
i].get_connections()) > 0:
@@ -517,7 +502,7 @@ class FlowGraph(Element):
for j in range(len(source.get_connections())):
sink.append(
- source.get_connections()[j].get_sink())
+ source.get_connections()[j].sink_port)
for elt in source.get_connections():
self.remove_element(elt)
for j in sink:
@@ -554,9 +539,9 @@ def _guess_file_format_1(n):
"""
try:
has_non_numeric_message_keys = any(not (
- connection_n.find('source_key').isdigit() and
- connection_n.find('sink_key').isdigit()
- ) for connection_n in n.find('flow_graph').findall('connection'))
+ connection_n.get('source_key', '').isdigit() and
+ connection_n.get('sink_key', '').isdigit()
+ ) for connection_n in n.get('flow_graph', []).get('connection', []))
if has_non_numeric_message_keys:
return 1
except:
@@ -570,21 +555,21 @@ def _initialize_dummy_block(block, block_n):
Modify block object to get the behaviour for a missing block
"""
- block._key = block_n.find('key')
+ block._key = block_n.get('key')
block.is_dummy_block = lambda: True
block.is_valid = lambda: False
block.get_enabled = lambda: False
- for param_n in block_n.findall('param'):
+ for param_n in block_n.get('param', []):
if param_n['key'] not in block.get_param_keys():
- new_param_n = odict({'key': param_n['key'], 'name': param_n['key'], 'type': 'string'})
- params = block.get_parent().get_parent().Param(block=block, n=new_param_n)
+ new_param_n = {'key': param_n['key'], 'name': param_n['key'], 'type': 'string'}
+ params = block.parent_platform.Param(block=block, n=new_param_n)
block.get_params().append(params)
def _dummy_block_add_port(block, key, dir):
""" This is so ugly... Add a port to a dummy-field block """
- port_n = odict({'name': '?', 'key': key, 'type': ''})
- port = block.get_parent().get_parent().Port(block=block, n=port_n, dir=dir)
+ port_n = {'name': '?', 'key': key, 'type': ''}
+ port = block.parent_platform.Port(block=block, n=port_n, dir=dir)
if port.is_source:
block.get_sources().append(port)
else:
diff --git a/grc/core/Messages.py b/grc/core/Messages.py
index 8daa12c33f..596b6197d8 100644
--- a/grc/core/Messages.py
+++ b/grc/core/Messages.py
@@ -16,9 +16,10 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+from __future__ import absolute_import
+
import traceback
import sys
-import os
# A list of functions that can receive a message.
MESSENGERS_LIST = list()
diff --git a/grc/core/Param.py b/grc/core/Param.py
index d155800c43..35bb176744 100644
--- a/grc/core/Param.py
+++ b/grc/core/Param.py
@@ -17,20 +17,20 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+from __future__ import absolute_import
+
import ast
import weakref
import re
+import collections
+
+from six.moves import builtins, filter, map, range, zip
from . import Constants
-from .Constants import VECTOR_TYPES, COMPLEX_TYPES, REAL_TYPES, INT_TYPES
from .Element import Element
-from .utils import odict
# Blacklist certain ids, its not complete, but should help
-import __builtin__
-
-
-ID_BLACKLIST = ['self', 'options', 'gr', 'blks2', 'wxgui', 'wx', 'math', 'forms', 'firdes'] + dir(__builtin__)
+ID_BLACKLIST = ['self', 'options', 'gr', 'blks2', 'wxgui', 'wx', 'math', 'forms', 'firdes'] + dir(builtins)
try:
from gnuradio import gr
ID_BLACKLIST.extend(attr for attr in dir(gr.top_block()) if not attr.startswith('_'))
@@ -64,7 +64,7 @@ def num_to_str(num):
return template.format(value / factor, symbol.strip())
return template.format(value, '')
- if isinstance(num, COMPLEX_TYPES):
+ if isinstance(num, Constants.COMPLEX_TYPES):
num = complex(num) # Cast to python complex
if num == 0:
return '0'
@@ -79,12 +79,12 @@ class Option(Element):
def __init__(self, param, n):
Element.__init__(self, param)
- self._name = n.find('name')
- self._key = n.find('key')
+ self._name = n.get('name')
+ self._key = n.get('key')
self._opts = dict()
- opts = n.findall('opt')
+ opts = n.get('opt', [])
# Test against opts when non enum
- if not self.get_parent().is_enum() and opts:
+ if not self.parent.is_enum() and opts:
raise Exception('Options for non-enum types cannot have sub-options')
# Extract opts
for opt in opts:
@@ -112,13 +112,13 @@ class Option(Element):
# Access Opts
##############################################
def get_opt_keys(self):
- return self._opts.keys()
+ return list(self._opts.keys())
def get_opt(self, key):
return self._opts[key]
def get_opts(self):
- return self._opts.values()
+ return list(self._opts.values())
class TemplateArg(object):
@@ -155,7 +155,7 @@ class Param(Element):
n: the nested odict
"""
# If the base key is a valid param key, copy its data and overlay this params data
- base_key = n.find('base_key')
+ base_key = n.get('base_key')
if base_key and base_key in block.get_param_keys():
n_expanded = block.get_param(base_key)._n.copy()
n_expanded.update(n)
@@ -163,20 +163,21 @@ class Param(Element):
# Save odict in case this param will be base for another
self._n = n
# Parse the data
- self._name = n.find('name')
- self._key = n.find('key')
- value = n.find('value') or ''
- self._type = n.find('type') or 'raw'
- self._hide = n.find('hide') or ''
- self._tab_label = n.find('tab') or block.get_param_tab_labels()[0]
+ self._name = n['name']
+ self._key = n['key']
+ value = n.get('value', '')
+ self._type = n.get('type', 'raw')
+ self._hide = n.get('hide', '')
+ self._tab_label = n.get('tab', block.get_param_tab_labels()[0])
if self._tab_label not in block.get_param_tab_labels():
block.get_param_tab_labels().append(self._tab_label)
# Build the param
- Element.__init__(self, block)
+ Element.__init__(self, parent=block)
# Create the Option objects from the n data
self._options = list()
self._evaluated = None
- for option in map(lambda o: Option(param=self, n=o), n.findall('option')):
+ for o_n in n.get('option', []):
+ option = Option(param=self, n=o_n)
key = option.get_key()
# Test against repeated keys
if key in self.get_option_keys():
@@ -257,9 +258,9 @@ class Param(Element):
t = self.get_type()
if isinstance(e, bool):
return str(e)
- elif isinstance(e, COMPLEX_TYPES):
+ elif isinstance(e, Constants.COMPLEX_TYPES):
dt_str = num_to_str(e)
- elif isinstance(e, VECTOR_TYPES):
+ elif isinstance(e, Constants.VECTOR_TYPES):
# Vector types
if len(e) > 8:
# Large vectors use code
@@ -292,38 +293,6 @@ class Param(Element):
def __str__(self):
return 'Param - {}({})'.format(self.get_name(), self.get_key())
- def get_color(self):
- """
- Get the color that represents this param's type.
-
- Returns:
- a hex color code.
- """
- try:
- return {
- # Number types
- 'complex': Constants.COMPLEX_COLOR_SPEC,
- 'real': Constants.FLOAT_COLOR_SPEC,
- 'float': Constants.FLOAT_COLOR_SPEC,
- 'int': Constants.INT_COLOR_SPEC,
- # Vector types
- 'complex_vector': Constants.COMPLEX_VECTOR_COLOR_SPEC,
- 'real_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
- 'float_vector': Constants.FLOAT_VECTOR_COLOR_SPEC,
- 'int_vector': Constants.INT_VECTOR_COLOR_SPEC,
- # Special
- 'bool': Constants.INT_COLOR_SPEC,
- 'hex': Constants.INT_COLOR_SPEC,
- 'string': Constants.BYTE_VECTOR_COLOR_SPEC,
- 'id': Constants.ID_COLOR_SPEC,
- 'stream_id': Constants.ID_COLOR_SPEC,
- 'grid_pos': Constants.INT_VECTOR_COLOR_SPEC,
- 'notebook': Constants.INT_VECTOR_COLOR_SPEC,
- 'raw': Constants.WILDCARD_COLOR_SPEC,
- }[self.get_type()]
- except:
- return '#FFFFFF'
-
def get_hide(self):
"""
Get the hide value from the base class.
@@ -335,20 +304,17 @@ class Param(Element):
Returns:
hide the hide property string
"""
- hide = self.get_parent().resolve_dependencies(self._hide).strip()
+ hide = self.parent.resolve_dependencies(self._hide).strip()
if hide:
return hide
# Hide ID in non variable blocks
- if self.get_key() == 'id' and not _show_id_matcher.match(self.get_parent().get_key()):
+ if self.get_key() == 'id' and not _show_id_matcher.match(self.parent.get_key()):
return 'part'
# Hide port controllers for type and nports
- if self.get_key() in ' '.join(map(lambda p: ' '.join([p._type, p._nports]),
- self.get_parent().get_ports())):
+ if self.get_key() in ' '.join([' '.join([p._type, p._nports]) for p in self.parent.get_ports()]):
return 'part'
# Hide port controllers for vlen, when == 1
- if self.get_key() in ' '.join(map(
- lambda p: p._vlen, self.get_parent().get_ports())
- ):
+ if self.get_key() in ' '.join(p._vlen for p in self.parent.get_ports()):
try:
if int(self.get_evaluated()) == 1:
return 'part'
@@ -371,7 +337,7 @@ class Param(Element):
self._evaluated = None
try:
self._evaluated = self.evaluate()
- except Exception, e:
+ except Exception as e:
self.add_error_message(str(e))
def get_evaluated(self):
@@ -403,22 +369,22 @@ class Param(Element):
elif t in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'):
# Raise exception if python cannot evaluate this value
try:
- e = self.get_parent().get_parent().evaluate(v)
- except Exception, e:
+ e = self.parent_flowgraph.evaluate(v)
+ except Exception as e:
raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e))
# Raise an exception if the data is invalid
if t == 'raw':
return e
elif t == 'complex':
- if not isinstance(e, COMPLEX_TYPES):
+ if not isinstance(e, Constants.COMPLEX_TYPES):
raise Exception('Expression "{}" is invalid for type complex.'.format(str(e)))
return e
elif t == 'real' or t == 'float':
- if not isinstance(e, REAL_TYPES):
+ if not isinstance(e, Constants.REAL_TYPES):
raise Exception('Expression "{}" is invalid for type float.'.format(str(e)))
return e
elif t == 'int':
- if not isinstance(e, INT_TYPES):
+ if not isinstance(e, Constants.INT_TYPES):
raise Exception('Expression "{}" is invalid for type integer.'.format(str(e)))
return e
elif t == 'hex':
@@ -438,29 +404,29 @@ class Param(Element):
v = '()'
# Raise exception if python cannot evaluate this value
try:
- e = self.get_parent().get_parent().evaluate(v)
- except Exception, e:
+ e = self.parent.parent.evaluate(v)
+ except Exception as e:
raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e))
# Raise an exception if the data is invalid
if t == 'complex_vector':
- if not isinstance(e, VECTOR_TYPES):
+ if not isinstance(e, Constants.VECTOR_TYPES):
self._lisitify_flag = True
e = [e]
- if not all([isinstance(ei, COMPLEX_TYPES) for ei in e]):
+ if not all([isinstance(ei, Constants.COMPLEX_TYPES) for ei in e]):
raise Exception('Expression "{}" is invalid for type complex vector.'.format(str(e)))
return e
elif t == 'real_vector' or t == 'float_vector':
- if not isinstance(e, VECTOR_TYPES):
+ if not isinstance(e, Constants.VECTOR_TYPES):
self._lisitify_flag = True
e = [e]
- if not all([isinstance(ei, REAL_TYPES) for ei in e]):
+ if not all([isinstance(ei, Constants.REAL_TYPES) for ei in e]):
raise Exception('Expression "{}" is invalid for type float vector.'.format(str(e)))
return e
elif t == 'int_vector':
- if not isinstance(e, VECTOR_TYPES):
+ if not isinstance(e, Constants.VECTOR_TYPES):
self._lisitify_flag = True
e = [e]
- if not all([isinstance(ei, INT_TYPES) for ei in e]):
+ if not all([isinstance(ei, Constants.INT_TYPES) for ei in e]):
raise Exception('Expression "{}" is invalid for type integer vector.'.format(str(e)))
return e
#########################
@@ -469,7 +435,7 @@ class Param(Element):
elif t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'):
# Do not check if file/directory exists, that is a runtime issue
try:
- e = self.get_parent().get_parent().evaluate(v)
+ e = self.parent.parent.evaluate(v)
if not isinstance(e, str):
raise Exception()
except:
@@ -500,16 +466,16 @@ class Param(Element):
elif t == 'stream_id':
# Get a list of all stream ids used in the virtual sinks
ids = [param.get_value() for param in filter(
- lambda p: p.get_parent().is_virtual_sink(),
+ lambda p: p.parent.is_virtual_sink(),
self.get_all_params(t),
)]
# Check that the virtual sink's stream id is unique
- if self.get_parent().is_virtual_sink():
+ if self.parent.is_virtual_sink():
# Id should only appear once, or zero times if block is disabled
if ids.count(v) > 1:
raise Exception('Stream ID "{}" is not unique.'.format(v))
# Check that the virtual source's steam id is found
- if self.get_parent().is_virtual_source():
+ if self.parent.is_virtual_source():
if v not in ids:
raise Exception('Stream ID "{}" is not found.'.format(v))
return v
@@ -557,7 +523,7 @@ class Param(Element):
if not v:
# Allow for empty grid pos
return ''
- e = self.get_parent().get_parent().evaluate(v)
+ e = self.parent_flowgraph.evaluate(v)
if not isinstance(e, (list, tuple)) or len(e) != 4 or not all([isinstance(ei, int) for ei in e]):
raise Exception('A grid position must be a list of 4 integers.')
row, col, row_span, col_span = e
@@ -569,7 +535,7 @@ class Param(Element):
raise Exception('Row and column span must be greater than zero.')
# Get hostage cell parent
try:
- my_parent = self.get_parent().get_param('notebook').evaluate()
+ my_parent = self.parent.get_param('notebook').evaluate()
except:
my_parent = ''
# Calculate hostage cells
@@ -577,7 +543,7 @@ class Param(Element):
for c in range(col_span):
self._hostage_cells.append((my_parent, (row+r, col+c)))
# Avoid collisions
- params = filter(lambda p: p is not self, self.get_all_params('grid_pos'))
+ params = [p for p in self.get_all_params('grid_pos') if p is not self]
for param in params:
for parent, cell in param._hostage_cells:
if (parent, cell) in self._hostage_cells:
@@ -592,7 +558,7 @@ class Param(Element):
return ''
# Get a list of all notebooks
- notebook_blocks = filter(lambda b: b.get_key() == 'notebook', self.get_parent().get_parent().get_enabled_blocks())
+ notebook_blocks = [b for b in self.parent_flowgraph.get_enabled_blocks() if b.get_key() == 'notebook']
# Check for notebook param syntax
try:
notebook_id, page_index = map(str.strip, v.split(','))
@@ -600,7 +566,7 @@ class Param(Element):
raise Exception('Bad notebook page format.')
# Check that the notebook id is valid
try:
- notebook_block = filter(lambda b: b.get_id() == notebook_id, notebook_blocks)[0]
+ notebook_block = [b for b in notebook_blocks if b.get_id() == notebook_id][0]
except:
raise Exception('Notebook id "{}" is not an existing notebook id.'.format(notebook_id))
@@ -616,12 +582,12 @@ class Param(Element):
# New namespace
n = dict()
try:
- exec v in n
+ exec(v, n)
except ImportError:
raise Exception('Import "{}" failed.'.format(v))
except Exception:
raise Exception('Bad import syntax: "{}".'.format(v))
- return filter(lambda k: str(k) != '__builtins__', n.keys())
+ return [k for k in list(n.keys()) if str(k) != '__builtins__']
#########################
else:
@@ -667,7 +633,10 @@ class Param(Element):
Returns:
a list of params
"""
- return sum([filter(lambda p: p.get_type() == type, block.get_params()) for block in self.get_parent().get_parent().get_enabled_blocks()], [])
+ params = []
+ for block in self.parent_flowgraph.get_enabled_blocks():
+ params.extend(p for p in block.get_params() if p.get_type() == type)
+ return params
def is_enum(self):
return self._type == 'enum'
@@ -689,13 +658,13 @@ class Param(Element):
self._default = str(value)
def get_type(self):
- return self.get_parent().resolve_dependencies(self._type)
+ return self.parent.resolve_dependencies(self._type)
def get_tab_label(self):
return self._tab_label
def get_name(self):
- return self.get_parent().resolve_dependencies(self._name).strip()
+ return self.parent.resolve_dependencies(self._name).strip()
def get_key(self):
return self._key
@@ -734,7 +703,7 @@ class Param(Element):
Returns:
a nested data odict
"""
- n = odict()
+ n = collections.OrderedDict()
n['key'] = self.get_key()
n['value'] = self.get_value()
return n
diff --git a/grc/core/ParseXML.py b/grc/core/ParseXML.py
index c9f6541ee7..163289ba06 100644
--- a/grc/core/ParseXML.py
+++ b/grc/core/ParseXML.py
@@ -17,9 +17,13 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+from __future__ import absolute_import
+
from lxml import etree
-from .utils import odict
+import six
+from six.moves import map
+
xml_failures = {}
etree.set_default_parser(etree.XMLParser(remove_comments=True))
@@ -75,17 +79,35 @@ def from_file(xml_file):
the nested data with grc version information
"""
xml = etree.parse(xml_file)
- nested_data = _from_file(xml.getroot())
+
+ tag, nested_data = _from_file(xml.getroot())
+ nested_data = {tag: nested_data, '_instructions': {}}
# Get the embedded instructions and build a dictionary item
- nested_data['_instructions'] = {}
xml_instructions = xml.xpath('/processing-instruction()')
- for inst in filter(lambda i: i.target == 'grc', xml_instructions):
- nested_data['_instructions'] = odict(inst.attrib)
+ for inst in xml_instructions:
+ if inst.target != 'grc':
+ continue
+ nested_data['_instructions'] = dict(inst.attrib)
return nested_data
-def _from_file(xml):
+WANT_A_LIST = {
+ '/block': 'import callback param check sink source'.split(),
+ '/block/param_tab_order': 'tab'.split(),
+ '/block/param': 'option'.split(),
+ '/block/param/option': 'opt'.split(),
+ '/flow_graph': 'block connection'.split(),
+ '/flow_graph/block': 'param'.split(),
+ '/cat': 'cat block'.split(),
+ '/cat/cat': 'cat block'.split(),
+ '/cat/cat/cat': 'cat block'.split(),
+ '/cat/cat/cat/cat': 'cat block'.split(),
+ '/domain': 'connection'.split(),
+}
+
+
+def _from_file(xml, parent_tag=''):
"""
Recursively parse the xml tree into nested data format.
@@ -96,21 +118,24 @@ def _from_file(xml):
the nested data
"""
tag = xml.tag
+ tag_path = parent_tag + '/' + tag
+
if not len(xml):
- return odict({tag: xml.text or ''}) # store empty tags (text is None) as empty string
- nested_data = odict()
+ return tag, xml.text or '' # store empty tags (text is None) as empty string
+
+ nested_data = {}
for elem in xml:
- key, value = _from_file(elem).items()[0]
- if key in nested_data:
- nested_data[key].append(value)
+ key, value = _from_file(elem, tag_path)
+
+ if key in WANT_A_LIST.get(tag_path, []):
+ try:
+ nested_data[key].append(value)
+ except KeyError:
+ nested_data[key] = [value]
else:
- nested_data[key] = [value]
- # Delistify if the length of values is 1
- for key, values in nested_data.iteritems():
- if len(values) == 1:
- nested_data[key] = values[0]
+ nested_data[key] = value
- return odict({tag: nested_data})
+ return tag, nested_data
def to_file(nested_data, xml_file):
@@ -127,7 +152,7 @@ def to_file(nested_data, xml_file):
if instructions:
xml_data += etree.tostring(etree.ProcessingInstruction(
'grc', ' '.join(
- "{0}='{1}'".format(*item) for item in instructions.iteritems())
+ "{0}='{1}'".format(*item) for item in six.iteritems(instructions))
), xml_declaration=True, pretty_print=True, encoding='utf-8')
xml_data += etree.tostring(_to_file(nested_data)[0],
pretty_print=True, encoding='utf-8')
@@ -146,14 +171,14 @@ def _to_file(nested_data):
the xml tree filled with child nodes
"""
nodes = list()
- for key, values in nested_data.iteritems():
+ for key, values in six.iteritems(nested_data):
# Listify the values if not a list
if not isinstance(values, (list, set, tuple)):
values = [values]
for value in values:
node = etree.Element(key)
- if isinstance(value, (str, unicode)):
- node.text = unicode(value)
+ if isinstance(value, (str, six.text_type)):
+ node.text = six.text_type(value)
else:
node.extend(_to_file(value))
nodes.append(node)
diff --git a/grc/core/Platform.py b/grc/core/Platform.py
index 9b25e67d65..be7b60ca59 100644
--- a/grc/core/Platform.py
+++ b/grc/core/Platform.py
@@ -17,9 +17,14 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+from __future__ import absolute_import, print_function
+
import os
import sys
+import six
+from six.moves import range
+
from . import ParseXML, Messages, Constants
from .Config import Config
@@ -31,7 +36,7 @@ from .Block import Block
from .Port import Port
from .Param import Param
-from .utils import odict, extract_docs
+from .utils import extract_docs
class Platform(Element):
@@ -48,10 +53,9 @@ class Platform(Element):
def __init__(self, *args, **kwargs):
""" Make a platform for GNU Radio """
- Element.__init__(self)
+ Element.__init__(self, parent=None)
self.config = self.Config(*args, **kwargs)
-
self.block_docstrings = {}
self.block_docstrings_loaded_callback = lambda: None # dummy to be replaced by BlockTreeWindow
@@ -63,18 +67,19 @@ class Platform(Element):
self._block_dtd = Constants.BLOCK_DTD
self._default_flow_graph = Constants.DEFAULT_FLOW_GRAPH
- # Create a dummy flow graph for the blocks
- self._flow_graph = Element(self)
- self._flow_graph.connections = []
-
- self.blocks = odict()
- self._blocks_n = odict()
+ self.blocks = {}
+ self._blocks_n = {}
self._block_categories = {}
self.domains = {}
self.connection_templates = {}
self._auto_hier_block_generate_chain = set()
+ # Create a dummy flow graph for the blocks
+ self._flow_graph = Element.__new__(FlowGraph)
+ Element.__init__(self._flow_graph, self)
+ self._flow_graph.connections = []
+
self.build_block_library()
def __str__(self):
@@ -130,12 +135,13 @@ class Platform(Element):
Messages.send('>>> Generate Error: {}: {}\n'.format(file_path, str(e)))
return False
- self.load_block_xml(generator.get_file_path_xml())
+ self.load_block_xml(generator.file_path_xml)
return True
def build_block_library(self):
"""load the blocks and block tree from the search paths"""
self._docstring_extractor.start()
+
# Reset
self.blocks.clear()
self._blocks_n.clear()
@@ -157,10 +163,10 @@ class Platform(Element):
# print >> sys.stderr, 'Warning: Block validation failed:\n\t%s\n\tIgnoring: %s' % (e, xml_file)
pass
except Exception as e:
- print >> sys.stderr, 'Warning: XML parsing failed:\n\t%r\n\tIgnoring: %s' % (e, xml_file)
+ print('Warning: XML parsing failed:\n\t%r\n\tIgnoring: %s' % (e, xml_file), file=sys.stderr)
# Add blocks to block tree
- for key, block in self.blocks.iteritems():
+ for key, block in six.iteritems(self.blocks):
category = self._block_categories.get(key, block.category)
# Blocks with empty categories are hidden
if not category:
@@ -182,20 +188,20 @@ class Platform(Element):
yield block_path
elif os.path.isdir(block_path):
for dirpath, dirnames, filenames in os.walk(block_path):
- for filename in sorted(filter(lambda f: f.endswith('.xml'), filenames)):
+ for filename in sorted(f for f in filenames if f.endswith('.xml')):
yield os.path.join(dirpath, filename)
def load_block_xml(self, xml_file):
"""Load block description from xml file"""
# Validate and import
ParseXML.validate_dtd(xml_file, self._block_dtd)
- n = ParseXML.from_file(xml_file).find('block')
+ n = ParseXML.from_file(xml_file).get('block', {})
n['block_wrapper_path'] = xml_file # inject block wrapper path
# Get block instance and add it to the list of blocks
block = self.Block(self._flow_graph, n)
key = block.get_key()
if key in self.blocks:
- print >> sys.stderr, 'Warning: Block with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file)
+ print('Warning: Block with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file), file=sys.stderr)
else: # Store the block
self.blocks[key] = block
self._blocks_n[key] = n
@@ -213,62 +219,62 @@ class Platform(Element):
path = []
def load_category(cat_n):
- path.append(cat_n.find('name').strip())
- for block_key in cat_n.findall('block'):
+ path.append(cat_n.get('name').strip())
+ for block_key in cat_n.get('block', []):
if block_key not in self._block_categories:
self._block_categories[block_key] = list(path)
- for sub_cat_n in cat_n.findall('cat'):
+ for sub_cat_n in cat_n.get('cat', []):
load_category(sub_cat_n)
path.pop()
- load_category(xml.find('cat'))
+ load_category(xml.get('cat', {}))
def load_domain_xml(self, xml_file):
"""Load a domain properties and connection templates from XML"""
ParseXML.validate_dtd(xml_file, Constants.DOMAIN_DTD)
- n = ParseXML.from_file(xml_file).find('domain')
+ n = ParseXML.from_file(xml_file).get('domain')
- key = n.find('key')
+ key = n.get('key')
if not key:
- print >> sys.stderr, 'Warning: Domain with emtpy key.\n\tIgnoring: {}'.format(xml_file)
+ print('Warning: Domain with emtpy key.\n\tIgnoring: {}'.format(xml_file), file=sys.stderr)
return
if key in self.domains: # test against repeated keys
- print >> sys.stderr, 'Warning: Domain with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file)
+ print('Warning: Domain with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file), file=sys.stderr)
return
- #to_bool = lambda s, d: d if s is None else s.lower() not in ('false', 'off', '0', '')
+ # to_bool = lambda s, d: d if s is None else s.lower() not in ('false', 'off', '0', '')
def to_bool(s, d):
if s is not None:
return s.lower() not in ('false', 'off', '0', '')
return d
- color = n.find('color') or ''
+ color = n.get('color') or ''
try:
- import gtk # ugly but handy
- gtk.gdk.color_parse(color)
- except (ValueError, ImportError):
+ chars_per_color = 2 if len(color) > 4 else 1
+ tuple(int(color[o:o + 2], 16) / 255.0 for o in range(1, 3 * chars_per_color, chars_per_color))
+ except ValueError:
if color: # no color is okay, default set in GUI
- print >> sys.stderr, 'Warning: Can\'t parse color code "{}" for domain "{}" '.format(color, key)
+ print('Warning: Can\'t parse color code "{}" for domain "{}" '.format(color, key), file=sys.stderr)
color = None
self.domains[key] = dict(
- name=n.find('name') or key,
- multiple_sinks=to_bool(n.find('multiple_sinks'), True),
- multiple_sources=to_bool(n.find('multiple_sources'), False),
+ name=n.get('name') or key,
+ multiple_sinks=to_bool(n.get('multiple_sinks'), True),
+ multiple_sources=to_bool(n.get('multiple_sources'), False),
color=color
)
- for connection_n in n.findall('connection'):
- key = (connection_n.find('source_domain'), connection_n.find('sink_domain'))
+ for connection_n in n.get('connection', []):
+ key = (connection_n.get('source_domain'), connection_n.get('sink_domain'))
if not all(key):
- print >> sys.stderr, 'Warning: Empty domain key(s) in connection template.\n\t{}'.format(xml_file)
+ print('Warning: Empty domain key(s) in connection template.\n\t{}'.format(xml_file), file=sys.stderr)
elif key in self.connection_templates:
- print >> sys.stderr, 'Warning: Connection template "{}" already exists.\n\t{}'.format(key, xml_file)
+ print('Warning: Connection template "{}" already exists.\n\t{}'.format(key, xml_file), file=sys.stderr)
else:
- self.connection_templates[key] = connection_n.find('make') or ''
+ self.connection_templates[key] = connection_n.get('make') or ''
def _save_docstring_extraction_result(self, key, docstrings):
docs = {}
- for match, docstring in docstrings.iteritems():
+ for match, docstring in six.iteritems(docstrings):
if not docstring or match.endswith('_sptr'):
continue
docstring = docstring.replace('\n\n', '\n').strip()
@@ -300,7 +306,7 @@ class Platform(Element):
return self.FlowGraph(platform=self)
def get_blocks(self):
- return self.blocks.values()
+ return list(self.blocks.values())
def get_new_block(self, flow_graph, key):
return self.Block(flow_graph, n=self._blocks_n[key])
diff --git a/grc/core/Port.py b/grc/core/Port.py
index 6a8f484082..9a33c5c506 100644
--- a/grc/core/Port.py
+++ b/grc/core/Port.py
@@ -17,7 +17,10 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-from .Constants import DEFAULT_DOMAIN, GR_STREAM_DOMAIN, GR_MESSAGE_DOMAIN
+from __future__ import absolute_import
+
+from six.moves import filter
+
from .Element import Element
from . import Constants
@@ -30,7 +33,7 @@ def _get_source_from_virtual_sink_port(vsp):
"""
try:
return _get_source_from_virtual_source_port(
- vsp.get_enabled_connections()[0].get_source())
+ vsp.get_enabled_connections()[0].source_port)
except:
raise Exception('Could not resolve source for virtual sink port {}'.format(vsp))
@@ -40,20 +43,20 @@ def _get_source_from_virtual_source_port(vsp, traversed=[]):
Recursively resolve source ports over the virtual connections.
Keep track of traversed sources to avoid recursive loops.
"""
- if not vsp.get_parent().is_virtual_source():
+ if not vsp.parent.is_virtual_source():
return vsp
if vsp in traversed:
raise Exception('Loop found when resolving virtual source {}'.format(vsp))
try:
return _get_source_from_virtual_source_port(
_get_source_from_virtual_sink_port(
- filter( # Get all virtual sinks with a matching stream id
- lambda vs: vs.get_param('stream_id').get_value() == vsp.get_parent().get_param('stream_id').get_value(),
- filter( # Get all enabled blocks that are also virtual sinks
+ list(filter( # Get all virtual sinks with a matching stream id
+ lambda vs: vs.get_param('stream_id').get_value() == vsp.parent.get_param('stream_id').get_value(),
+ list(filter( # Get all enabled blocks that are also virtual sinks
lambda b: b.is_virtual_sink(),
- vsp.get_parent().get_parent().get_enabled_blocks(),
- ),
- )[0].get_sinks()[0]
+ vsp.parent.parent.get_enabled_blocks(),
+ )),
+ ))[0].get_sinks()[0]
), traversed + [vsp],
)
except:
@@ -68,7 +71,7 @@ def _get_sink_from_virtual_source_port(vsp):
try:
# Could have many connections, but use first
return _get_sink_from_virtual_sink_port(
- vsp.get_enabled_connections()[0].get_sink())
+ vsp.get_enabled_connections()[0].sink_port)
except:
raise Exception('Could not resolve source for virtual source port {}'.format(vsp))
@@ -78,7 +81,7 @@ def _get_sink_from_virtual_sink_port(vsp, traversed=[]):
Recursively resolve sink ports over the virtual connections.
Keep track of traversed sinks to avoid recursive loops.
"""
- if not vsp.get_parent().is_virtual_sink():
+ if not vsp.parent.is_virtual_sink():
return vsp
if vsp in traversed:
raise Exception('Loop found when resolving virtual sink {}'.format(vsp))
@@ -86,11 +89,11 @@ def _get_sink_from_virtual_sink_port(vsp, traversed=[]):
return _get_sink_from_virtual_sink_port(
_get_sink_from_virtual_source_port(
filter( # Get all virtual source with a matching stream id
- lambda vs: vs.get_param('stream_id').get_value() == vsp.get_parent().get_param('stream_id').get_value(),
- filter( # Get all enabled blocks that are also virtual sinks
+ lambda vs: vs.get_param('stream_id').get_value() == vsp.parent.get_param('stream_id').get_value(),
+ list(filter( # Get all enabled blocks that are also virtual sinks
lambda b: b.is_virtual_source(),
- vsp.get_parent().get_parent().get_enabled_blocks(),
- ),
+ vsp.parent.parent.get_enabled_blocks(),
+ )),
)[0].get_sources()[0]
), traversed + [vsp],
)
@@ -113,31 +116,32 @@ class Port(Element):
"""
self._n = n
if n['type'] == 'message':
- n['domain'] = GR_MESSAGE_DOMAIN
+ n['domain'] = Constants.GR_MESSAGE_DOMAIN
if 'domain' not in n:
- n['domain'] = DEFAULT_DOMAIN
- elif n['domain'] == GR_MESSAGE_DOMAIN:
+ n['domain'] = Constants.DEFAULT_DOMAIN
+ elif n['domain'] == Constants.GR_MESSAGE_DOMAIN:
n['key'] = n['name']
n['type'] = 'message' # For port color
if n['type'] == 'msg':
n['key'] = 'msg'
- if not n.find('key'):
- n['key'] = str(next(block.port_counters[dir == 'source']))
+
+ n.setdefault('key', str(next(block.port_counters[dir == 'source'])))
# Build the port
- Element.__init__(self, block)
+ Element.__init__(self, parent=block)
# Grab the data
self._name = n['name']
self._key = n['key']
- self._type = n['type'] or ''
- self._domain = n['domain']
- self._hide = n.find('hide') or ''
+ self._type = n.get('type', '')
+ self._domain = n.get('domain')
+ self._hide = n.get('hide', '')
self._dir = dir
self._hide_evaluated = False # Updated on rewrite()
- self._nports = n.find('nports') or ''
- self._vlen = n.find('vlen') or ''
- self._optional = bool(n.find('optional'))
+ self._nports = n.get('nports', '')
+ self._vlen = n.get('vlen', '')
+ self._optional = bool(n.get('optional'))
+ self.di_optional = bool(n.get('optional'))
self._clones = [] # References to cloned ports (for nports > 1)
def __str__(self):
@@ -147,7 +151,7 @@ class Port(Element):
return 'Sink - {}({})'.format(self.get_name(), self.get_key())
def get_types(self):
- return Constants.TYPE_TO_SIZEOF.keys()
+ return list(Constants.TYPE_TO_SIZEOF.keys())
def is_type_empty(self):
return not self._n['type']
@@ -156,7 +160,7 @@ class Port(Element):
Element.validate(self)
if self.get_type() not in self.get_types():
self.add_error_message('Type "{}" is not a possible type.'.format(self.get_type()))
- platform = self.get_parent().get_parent().get_parent()
+ platform = self.parent.parent.parent
if self.get_domain() not in platform.domains:
self.add_error_message('Domain key "{}" is not registered.'.format(self.get_domain()))
if not self.get_enabled_connections() and not self.get_optional():
@@ -184,22 +188,22 @@ class Port(Element):
self._vlen = ''
Element.rewrite(self)
- hide = self.get_parent().resolve_dependencies(self._hide).strip().lower()
+ hide = self.parent.resolve_dependencies(self._hide).strip().lower()
self._hide_evaluated = False if hide in ('false', 'off', '0') else bool(hide)
# Update domain if was deduced from (dynamic) port type
type_ = self.get_type()
- if self._domain == GR_STREAM_DOMAIN and type_ == "message":
- self._domain = GR_MESSAGE_DOMAIN
+ if self._domain == Constants.GR_STREAM_DOMAIN and type_ == "message":
+ self._domain = Constants.GR_MESSAGE_DOMAIN
self._key = self._name
- if self._domain == GR_MESSAGE_DOMAIN and type_ != "message":
- self._domain = GR_STREAM_DOMAIN
+ if self._domain == Constants.GR_MESSAGE_DOMAIN and type_ != "message":
+ self._domain = Constants.GR_STREAM_DOMAIN
self._key = '0' # Is rectified in rewrite()
def resolve_virtual_source(self):
- if self.get_parent().is_virtual_sink():
+ if self.parent.is_virtual_sink():
return _get_source_from_virtual_sink_port(self)
- if self.get_parent().is_virtual_source():
+ if self.parent.is_virtual_source():
return _get_source_from_virtual_source_port(self)
def resolve_empty_type(self):
@@ -232,9 +236,9 @@ class Port(Element):
Returns:
the vector length or 1
"""
- vlen = self.get_parent().resolve_dependencies(self._vlen)
+ vlen = self.parent.resolve_dependencies(self._vlen)
try:
- return int(self.get_parent().get_parent().evaluate(vlen))
+ return int(self.parent.parent.evaluate(vlen))
except:
return 1
@@ -250,9 +254,9 @@ class Port(Element):
if self._nports == '':
return ''
- nports = self.get_parent().resolve_dependencies(self._nports)
+ nports = self.parent.resolve_dependencies(self._nports)
try:
- return max(1, int(self.get_parent().get_parent().evaluate(nports)))
+ return max(1, int(self.parent.parent.evaluate(nports)))
except:
return 1
@@ -321,7 +325,7 @@ class Port(Element):
n['key'] = '99999' if self._key.isdigit() else n['name']
# Clone
- port = self.__class__(self.get_parent(), n, self._dir)
+ port = self.__class__(self.parent, n, self._dir)
self._clones.append(port)
return port
@@ -341,7 +345,7 @@ class Port(Element):
def get_name(self):
number = ''
if self.get_type() == 'bus':
- busses = filter(lambda a: a._dir == self._dir, self.get_parent().get_ports_gui())
+ busses = [a for a in self.parent.get_ports_gui() if a._dir == self._dir]
number = str(busses.index(self)) + '#' + str(len(self.get_associated_ports()))
return self._name + number
@@ -357,7 +361,7 @@ class Port(Element):
return self._dir == 'source'
def get_type(self):
- return self.get_parent().resolve_dependencies(self._type)
+ return self.parent_block.resolve_dependencies(self._type)
def get_domain(self):
return self._domain
@@ -372,8 +376,8 @@ class Port(Element):
Returns:
a list of connection objects
"""
- connections = self.get_parent().get_parent().connections
- connections = filter(lambda c: c.get_source() is self or c.get_sink() is self, connections)
+ connections = self.parent_flowgraph.connections
+ connections = [c for c in connections if c.source_port is self or c.sink_port is self]
return connections
def get_enabled_connections(self):
@@ -383,22 +387,23 @@ class Port(Element):
Returns:
a list of connection objects
"""
- return filter(lambda c: c.get_enabled(), self.get_connections())
+ return [c for c in self.get_connections() if c.get_enabled()]
def get_associated_ports(self):
if not self.get_type() == 'bus':
return [self]
else:
+ flowgraph = self.parent_flowgraph
if self.is_source:
- get_ports = self.get_parent().get_sources
- bus_structure = self.get_parent().current_bus_structure['source']
+ get_ports = flowgraph.get_sources
+ bus_structure = flowgraph.current_bus_structure['source']
else:
- get_ports = self.get_parent().get_sinks
- bus_structure = self.get_parent().current_bus_structure['sink']
+ get_ports = flowgraph.get_sinks
+ bus_structure = flowgraph.current_bus_structure['sink']
ports = [i for i in get_ports() if not i.get_type() == 'bus']
if bus_structure:
busses = [i for i in get_ports() if i.get_type() == 'bus']
bus_index = busses.index(self)
- ports = filter(lambda a: ports.index(a) in bus_structure[bus_index], ports)
+ ports = [a for a in ports if ports.index(a) in bus_structure[bus_index]]
return ports
diff --git a/grc/core/generator/FlowGraphProxy.py b/grc/core/generator/FlowGraphProxy.py
index 3723005576..a23c6d84ab 100644
--- a/grc/core/generator/FlowGraphProxy.py
+++ b/grc/core/generator/FlowGraphProxy.py
@@ -16,6 +16,10 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+from __future__ import absolute_import
+from six.moves import range
+
+
class FlowGraphProxy(object):
def __init__(self, fg):
@@ -34,7 +38,7 @@ class FlowGraphProxy(object):
Returns:
a list of dicts with: type, label, vlen, size, optional
"""
- return filter(lambda p: p['type'] != "message", self.get_hier_block_io(direction))
+ return [p for p in self.get_hier_block_io(direction) if p['type'] != "message"]
def get_hier_block_message_io(self, direction):
"""
@@ -46,7 +50,7 @@ class FlowGraphProxy(object):
Returns:
a list of dicts with: type, label, vlen, size, optional
"""
- return filter(lambda p: p['type'] == "message", self.get_hier_block_io(direction))
+ return [p for p in self.get_hier_block_io(direction) if p['type'] == "message"]
def get_hier_block_io(self, direction):
"""
@@ -71,7 +75,7 @@ class FlowGraphProxy(object):
}
num_ports = pad.get_param('num_streams').get_evaluated()
if num_ports > 1:
- for i in xrange(num_ports):
+ for i in range(num_ports):
clone = master.copy()
clone['label'] += str(i)
ports.append(clone)
@@ -86,7 +90,7 @@ class FlowGraphProxy(object):
Returns:
a list of pad source blocks in this flow graph
"""
- pads = filter(lambda b: b.get_key() == 'pad_source', self.get_enabled_blocks())
+ pads = [b for b in self.get_enabled_blocks() if b.get_key() == 'pad_source']
return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id()))
def get_pad_sinks(self):
@@ -96,7 +100,7 @@ class FlowGraphProxy(object):
Returns:
a list of pad sink blocks in this flow graph
"""
- pads = filter(lambda b: b.get_key() == 'pad_sink', self.get_enabled_blocks())
+ pads = [b for b in self.get_enabled_blocks() if b.get_key() == 'pad_sink']
return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id()))
def get_pad_port_global_key(self, port):
@@ -113,7 +117,7 @@ class FlowGraphProxy(object):
# using the block param 'type' instead of the port domain here
# to emphasize that hier block generation is domain agnostic
is_message_pad = pad.get_param('type').get_evaluated() == "message"
- if port.get_parent() == pad:
+ if port.parent == pad:
if is_message_pad:
key = pad.get_param('label').get_value()
else:
@@ -123,4 +127,4 @@ class FlowGraphProxy(object):
# assuming we have either only sources or sinks
if not is_message_pad:
key_offset += len(pad.get_ports())
- return -1 \ No newline at end of file
+ return -1
diff --git a/grc/core/generator/Generator.py b/grc/core/generator/Generator.py
index fb7a3afb99..9c7d07e76b 100644
--- a/grc/core/generator/Generator.py
+++ b/grc/core/generator/Generator.py
@@ -16,11 +16,16 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+from __future__ import absolute_import
+
import codecs
import os
import tempfile
+import operator
+import collections
from Cheetah.Template import Template
+import six
from .FlowGraphProxy import FlowGraphProxy
from .. import ParseXML, Messages
@@ -28,7 +33,7 @@ from ..Constants import (
TOP_BLOCK_FILE_MODE, BLOCK_FLAG_NEED_QT_GUI,
HIER_BLOCK_FILE_MODE, BLOCK_DTD
)
-from ..utils import expr_utils, odict
+from ..utils import expr_utils
DATA_DIR = os.path.dirname(__file__)
FLOW_GRAPH_TEMPLATE = os.path.join(DATA_DIR, 'flow_graph.tmpl')
@@ -82,20 +87,18 @@ class TopBlockGenerator(object):
filename = self._flow_graph.get_option('id') + '.py'
self.file_path = os.path.join(dirname, filename)
- def get_file_path(self):
- return self.file_path
-
def write(self):
"""generate output and write it to files"""
# Do throttle warning
- throttling_blocks = filter(lambda b: b.throtteling(), self._flow_graph.get_enabled_blocks())
+ throttling_blocks = [b for b in self._flow_graph.get_enabled_blocks()
+ if b.throtteling()]
if not throttling_blocks and not self._generate_options.startswith('hb'):
Messages.send_warning("This flow graph may not have flow control: "
"no audio or RF hardware blocks found. "
"Add a Misc->Throttle block to your flow "
"graph to avoid CPU congestion.")
if len(throttling_blocks) > 1:
- keys = set(map(lambda b: b.get_key(), throttling_blocks))
+ keys = set([b.get_key() for b in throttling_blocks])
if len(keys) > 1 and 'blocks_throttle' in keys:
Messages.send_warning("This flow graph contains a throttle "
"block and another rate limiting block, "
@@ -142,15 +145,15 @@ class TopBlockGenerator(object):
return code
blocks_all = expr_utils.sort_objects(
- filter(lambda b: b.get_enabled() and not b.get_bypassed(), fg.blocks),
- lambda b: b.get_id(), _get_block_sort_text
+ [b for b in fg.blocks if b.get_enabled() and not b.get_bypassed()],
+ operator.methodcaller('get_id'), _get_block_sort_text
)
- deprecated_block_keys = set(block.get_name() for block in blocks_all if block.is_deprecated)
+ deprecated_block_keys = set(b.get_name() for b in blocks_all if b.is_deprecated)
for key in deprecated_block_keys:
Messages.send_warning("The block {!r} is deprecated.".format(key))
# List of regular blocks (all blocks minus the special ones)
- blocks = filter(lambda b: b not in (imports + parameters), blocks_all)
+ blocks = [b for b in blocks_all if b not in imports and b not in parameters]
for block in blocks:
key = block.get_key()
@@ -164,15 +167,15 @@ class TopBlockGenerator(object):
# Filter out virtual sink connections
def cf(c):
- return not (c.is_bus() or c.is_msg() or c.get_sink().get_parent().is_virtual_sink())
- connections = filter(cf, fg.get_enabled_connections())
+ return not (c.is_bus() or c.is_msg() or c.sink_block.is_virtual_sink())
+ connections = [con for con in fg.get_enabled_connections() if cf(con)]
# Get the virtual blocks and resolve their connections
- virtual = filter(lambda c: c.get_source().get_parent().is_virtual_source(), connections)
+ virtual = [c for c in connections if c.source_block.is_virtual_source()]
for connection in virtual:
- source = connection.get_source().resolve_virtual_source()
- sink = connection.get_sink()
- resolved = fg.get_parent().Connection(flow_graph=fg, porta=source, portb=sink)
+ source = connection.source.resolve_virtual_source()
+ sink = connection.sink_port
+ resolved = fg.parent.Connection(flow_graph=fg, porta=source, portb=sink)
connections.append(resolved)
# Remove the virtual connection
connections.remove(connection)
@@ -186,20 +189,20 @@ class TopBlockGenerator(object):
for block in bypassed_blocks:
# Get the upstream connection (off of the sink ports)
# Use *connections* not get_connections()
- source_connection = filter(lambda c: c.get_sink() == block.get_sinks()[0], connections)
+ source_connection = [c for c in connections if c.sink_port == block.get_sinks()[0]]
# The source connection should never have more than one element.
assert (len(source_connection) == 1)
# Get the source of the connection.
- source_port = source_connection[0].get_source()
+ source_port = source_connection[0].source_port
# Loop through all the downstream connections
- for sink in filter(lambda c: c.get_source() == block.get_sources()[0], connections):
+ for sink in (c for c in connections if c.source_port == block.get_sources()[0]):
if not sink.get_enabled():
# Ignore disabled connections
continue
- sink_port = sink.get_sink()
- connection = fg.get_parent().Connection(flow_graph=fg, porta=source_port, portb=sink_port)
+ sink_port = sink.sink_port
+ connection = fg.parent.Connection(flow_graph=fg, porta=source_port, portb=sink_port)
connections.append(connection)
# Remove this sink connection
connections.remove(sink)
@@ -208,12 +211,12 @@ class TopBlockGenerator(object):
# List of connections where each endpoint is enabled (sorted by domains, block names)
connections.sort(key=lambda c: (
- c.get_source().get_domain(), c.get_sink().get_domain(),
- c.get_source().get_parent().get_id(), c.get_sink().get_parent().get_id()
+ c.source_port.get_domain(), c.sink_port.get_domain(),
+ c.source_block.get_id(), c.sink_block.get_id()
))
- connection_templates = fg.get_parent().connection_templates
- msgs = filter(lambda c: c.is_msg(), fg.get_enabled_connections())
+ connection_templates = fg.parent.connection_templates
+ msgs = [c for c in fg.get_enabled_connections() if c.is_msg()]
# List of variable names
var_ids = [var.get_id() for var in parameters + variables]
@@ -264,7 +267,7 @@ class HierBlockGenerator(TopBlockGenerator):
file_path: where to write the py file (the xml goes into HIER_BLOCK_LIB_DIR)
"""
TopBlockGenerator.__init__(self, flow_graph, file_path)
- platform = flow_graph.get_parent()
+ platform = flow_graph.parent
hier_block_lib_dir = platform.config.hier_block_lib_dir
if not os.path.exists(hier_block_lib_dir):
@@ -272,18 +275,15 @@ class HierBlockGenerator(TopBlockGenerator):
self._mode = HIER_BLOCK_FILE_MODE
self.file_path = os.path.join(hier_block_lib_dir, self._flow_graph.get_option('id') + '.py')
- self._file_path_xml = self.file_path + '.xml'
-
- def get_file_path_xml(self):
- return self._file_path_xml
+ self.file_path_xml = self.file_path + '.xml'
def write(self):
"""generate output and write it to files"""
TopBlockGenerator.write(self)
- ParseXML.to_file(self._build_block_n_from_flow_graph_io(), self.get_file_path_xml())
- ParseXML.validate_dtd(self.get_file_path_xml(), BLOCK_DTD)
+ ParseXML.to_file(self._build_block_n_from_flow_graph_io(), self.file_path_xml)
+ ParseXML.validate_dtd(self.file_path_xml, BLOCK_DTD)
try:
- os.chmod(self.get_file_path_xml(), self._mode)
+ os.chmod(self.file_path_xml, self._mode)
except:
pass
@@ -299,12 +299,12 @@ class HierBlockGenerator(TopBlockGenerator):
parameters = self._flow_graph.get_parameters()
def var_or_value(name):
- if name in map(lambda p: p.get_id(), parameters):
- return "$"+name
+ if name in (p.get_id() for p in parameters):
+ return "$" + name
return name
# Build the nested data
- block_n = odict()
+ block_n = collections.OrderedDict()
block_n['name'] = self._flow_graph.get_option('title') or \
self._flow_graph.get_option('id').replace('_', ' ').title()
block_n['key'] = block_key
@@ -329,7 +329,7 @@ class HierBlockGenerator(TopBlockGenerator):
# Parameters
block_n['param'] = list()
for param in parameters:
- param_n = odict()
+ param_n = collections.OrderedDict()
param_n['name'] = param.get_param('label').get_value() or param.get_id()
param_n['key'] = param.get_id()
param_n['value'] = param.get_param('value').get_value()
@@ -346,7 +346,7 @@ class HierBlockGenerator(TopBlockGenerator):
for direction in ('sink', 'source'):
block_n[direction] = list()
for port in self._flow_graph.get_hier_block_io(direction):
- port_n = odict()
+ port_n = collections.OrderedDict()
port_n['name'] = port['label']
port_n['type'] = port['type']
if port['type'] != "message":
@@ -379,14 +379,18 @@ class QtHierBlockGenerator(HierBlockGenerator):
def _build_block_n_from_flow_graph_io(self):
n = HierBlockGenerator._build_block_n_from_flow_graph_io(self)
- block_n = n['block']
+ block_n = collections.OrderedDict()
+
+ # insert flags after category
+ for key, value in six.iteritems(n['block']):
+ block_n[key] = value
+ if key == 'category':
+ block_n['flags'] = BLOCK_FLAG_NEED_QT_GUI
if not block_n['name'].upper().startswith('QT GUI'):
block_n['name'] = 'QT GUI ' + block_n['name']
- block_n.insert_after('category', 'flags', BLOCK_FLAG_NEED_QT_GUI)
-
- gui_hint_param = odict()
+ gui_hint_param = collections.OrderedDict()
gui_hint_param['name'] = 'GUI Hint'
gui_hint_param['key'] = 'gui_hint'
gui_hint_param['value'] = ''
@@ -398,4 +402,5 @@ class QtHierBlockGenerator(HierBlockGenerator):
"\n#set $win = 'self.%s' % $id"
"\n${gui_hint()($win)}"
)
- return n
+
+ return {'block': block_n}
diff --git a/grc/core/generator/__init__.py b/grc/core/generator/__init__.py
index f44b94a85d..98f410c8d4 100644
--- a/grc/core/generator/__init__.py
+++ b/grc/core/generator/__init__.py
@@ -15,4 +15,5 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-from Generator import Generator
+from __future__ import absolute_import
+from .Generator import Generator
diff --git a/grc/core/generator/flow_graph.tmpl b/grc/core/generator/flow_graph.tmpl
index 436e3bbf0d..5eef3d2042 100644
--- a/grc/core/generator/flow_graph.tmpl
+++ b/grc/core/generator/flow_graph.tmpl
@@ -205,7 +205,7 @@ gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))])
$DIVIDER
#end if
#for $msg in $msgs
- $(msg.get_source().get_parent().get_id())_msgq_out = $(msg.get_sink().get_parent().get_id())_msgq_in = gr.msg_queue(2)
+ $(msg.source_block.get_id())_msgq_out = $(msg.sink_block.get_id())_msgq_in = gr.msg_queue(2)
#end for
########################################################
##Create Blocks
@@ -241,11 +241,11 @@ gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))])
## However, port names for IO pads should be self.
########################################################
#def make_port_sig($port)
- #if $port.get_parent().get_key() in ('pad_source', 'pad_sink')
+ #if $port.parent.get_key() in ('pad_source', 'pad_sink')
#set block = 'self'
#set key = $flow_graph.get_pad_port_global_key($port)
#else
- #set block = 'self.' + $port.get_parent().get_id()
+ #set block = 'self.' + $port.parent.get_id()
#set key = $port.get_key()
#end if
#if not $key.isdigit()
@@ -260,8 +260,8 @@ gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))])
$DIVIDER
#end if
#for $con in $connections
- #set global $source = $con.get_source()
- #set global $sink = $con.get_sink()
+ #set global $source = $con.source_port
+ #set global $sink = $con.sink_port
#include source=$connection_templates[($source.get_domain(), $sink.get_domain())]
#end for
diff --git a/grc/core/utils/__init__.py b/grc/core/utils/__init__.py
index 6b23da2723..66393fdc1b 100644
--- a/grc/core/utils/__init__.py
+++ b/grc/core/utils/__init__.py
@@ -15,8 +15,9 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-import expr_utils
-import epy_block_io
-import extract_docs
+from __future__ import absolute_import
+
+from . import expr_utils
+from . import epy_block_io
+from . import extract_docs
-from odict import odict
diff --git a/grc/core/utils/complexity.py b/grc/core/utils/complexity.py
index baa8040db4..d72db9b3dd 100644
--- a/grc/core/utils/complexity.py
+++ b/grc/core/utils/complexity.py
@@ -8,8 +8,8 @@ def calculate_flowgraph_complexity(flowgraph):
continue
# Don't worry about optional sinks?
- sink_list = filter(lambda c: not c.get_optional(), block.get_sinks())
- source_list = filter(lambda c: not c.get_optional(), block.get_sources())
+ sink_list = [c for c in block.get_sinks() if not c.get_optional()]
+ source_list = [c for c in block.get_sources() if not c.get_optional()]
sinks = float(len(sink_list))
sources = float(len(source_list))
base = max(min(sinks, sources), 1)
@@ -22,14 +22,15 @@ def calculate_flowgraph_complexity(flowgraph):
multi = 1
# Connection ratio multiplier
- sink_multi = max(float(sum(map(lambda c: len(c.get_connections()), sink_list)) / max(sinks, 1.0)), 1.0)
- source_multi = max(float(sum(map(lambda c: len(c.get_connections()), source_list)) / max(sources, 1.0)), 1.0)
- dbal = dbal + (base * multi * sink_multi * source_multi)
+ sink_multi = max(float(sum(len(c.get_connections()) for c in sink_list) / max(sinks, 1.0)), 1.0)
+ source_multi = max(float(sum(len(c.get_connections()) for c in source_list) / max(sources, 1.0)), 1.0)
+ dbal += base * multi * sink_multi * source_multi
blocks = float(len(flowgraph.blocks))
connections = float(len(flowgraph.connections))
elements = blocks + connections
- disabled_connections = len(filter(lambda c: not c.get_enabled(), flowgraph.connections))
+ disabled_connections = sum(not c.get_enabled() for c in flowgraph.connections)
+
variables = elements - blocks - connections
enabled = float(len(flowgraph.get_enabled_blocks()))
diff --git a/grc/core/utils/epy_block_io.py b/grc/core/utils/epy_block_io.py
index 76b50051db..7a2006a833 100644
--- a/grc/core/utils/epy_block_io.py
+++ b/grc/core/utils/epy_block_io.py
@@ -1,7 +1,12 @@
+from __future__ import absolute_import
+
import inspect
import collections
+import six
+from six.moves import zip
+
TYPE_MAP = {
'complex64': 'complex', 'complex': 'complex',
@@ -31,10 +36,10 @@ def _ports(sigs, msgs):
def _find_block_class(source_code, cls):
ns = {}
try:
- exec source_code in ns
+ exec(source_code, ns)
except Exception as e:
raise ValueError("Can't interpret source code: " + str(e))
- for var in ns.itervalues():
+ for var in six.itervalues(ns):
if inspect.isclass(var) and issubclass(var, cls):
return var
raise ValueError('No python block class found in code')
@@ -52,7 +57,7 @@ def extract(cls):
spec = inspect.getargspec(cls.__init__)
init_args = spec.args[1:]
- defaults = map(repr, spec.defaults or ())
+ defaults = [repr(arg) for arg in (spec.defaults or ())]
doc = cls.__doc__ or cls.__init__.__doc__ or ''
cls_name = cls.__name__
diff --git a/grc/core/utils/expr_utils.py b/grc/core/utils/expr_utils.py
index 2059ceff9f..555bd709b1 100644
--- a/grc/core/utils/expr_utils.py
+++ b/grc/core/utils/expr_utils.py
@@ -17,7 +17,12 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+from __future__ import absolute_import, print_function
+
import string
+
+import six
+
VAR_CHARS = string.letters + string.digits + '_'
@@ -50,7 +55,7 @@ class graph(object):
self._graph[src_node_key].remove(dest_node_key)
def get_nodes(self):
- return self._graph.keys()
+ return list(self._graph.keys())
def get_edges(self, node_key):
return self._graph[node_key]
@@ -85,7 +90,7 @@ def expr_split(expr, var_chars=VAR_CHARS):
toks.append(char)
tok = ''
toks.append(tok)
- return filter(lambda t: t, toks)
+ return [t for t in toks if t]
def expr_replace(expr, replace_dict):
@@ -101,7 +106,7 @@ def expr_replace(expr, replace_dict):
"""
expr_splits = expr_split(expr, var_chars=VAR_CHARS + '.')
for i, es in enumerate(expr_splits):
- if es in replace_dict.keys():
+ if es in list(replace_dict.keys()):
expr_splits[i] = replace_dict[es]
return ''.join(expr_splits)
@@ -118,7 +123,7 @@ def get_variable_dependencies(expr, vars):
a subset of vars used in the expression
"""
expr_toks = expr_split(expr)
- return set(var for var in vars if var in expr_toks)
+ return set(v for v in vars if v in expr_toks)
def get_graph(exprs):
@@ -131,12 +136,12 @@ def get_graph(exprs):
Returns:
a graph of variable deps
"""
- vars = exprs.keys()
+ vars = list(exprs.keys())
# Get dependencies for each expression, load into graph
var_graph = graph()
for var in vars:
var_graph.add_node(var)
- for var, expr in exprs.iteritems():
+ for var, expr in six.iteritems(exprs):
for dep in get_variable_dependencies(expr, vars):
if dep != var:
var_graph.add_edge(dep, var)
@@ -159,7 +164,7 @@ def sort_variables(exprs):
# Determine dependency order
while var_graph.get_nodes():
# Get a list of nodes with no edges
- indep_vars = filter(lambda var: not var_graph.get_edges(var), var_graph.get_nodes())
+ indep_vars = [var for var in var_graph.get_nodes() if not var_graph.get_edges(var)]
if not indep_vars:
raise Exception('circular dependency caught in sort_variables')
# Add the indep vars to the end of the list
@@ -189,3 +194,4 @@ def sort_objects(objects, get_id, get_expr):
sorted_ids = sort_variables(id2expr)
# Return list of sorted objects
return [id2obj[id] for id in sorted_ids]
+
diff --git a/grc/core/utils/extract_docs.py b/grc/core/utils/extract_docs.py
index a6e0bc971e..cff8a81099 100644
--- a/grc/core/utils/extract_docs.py
+++ b/grc/core/utils/extract_docs.py
@@ -17,15 +17,19 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+from __future__ import absolute_import, print_function
+
import sys
import re
import subprocess
import threading
import json
-import Queue
import random
import itertools
+import six
+from six.moves import queue, filter, range
+
###############################################################################
# The docstring extraction
@@ -124,7 +128,7 @@ class SubprocessLoader(object):
self.callback_query_result = callback_query_result
self.callback_finished = callback_finished or (lambda: None)
- self._queue = Queue.Queue()
+ self._queue = queue.Queue()
self._thread = None
self._worker = None
self._shutdown = threading.Event()
@@ -157,14 +161,14 @@ class SubprocessLoader(object):
cmd, args = self._last_cmd
if cmd == 'query':
msg += " (crashed while loading {0!r})".format(args[0])
- print >> sys.stderr, msg
+ print(msg, file=sys.stderr)
continue # restart
else:
break # normal termination, return
finally:
self._worker.terminate()
else:
- print >> sys.stderr, "Warning: docstring loader crashed too often"
+ print("Warning: docstring loader crashed too often", file=sys.stderr)
self._thread = None
self._worker = None
self.callback_finished()
@@ -203,9 +207,9 @@ class SubprocessLoader(object):
key, docs = args
self.callback_query_result(key, docs)
elif cmd == 'error':
- print args
+ print(args)
else:
- print >> sys.stderr, "Unknown response:", cmd, args
+ print("Unknown response:", cmd, args, file=sys.stderr)
def query(self, key, imports=None, make=None):
""" Request docstring extraction for a certain key """
@@ -270,12 +274,12 @@ if __name__ == '__worker__':
elif __name__ == '__main__':
def callback(key, docs):
- print key
- for match, doc in docs.iteritems():
- print '-->', match
- print doc.strip()
- print
- print
+ print(key)
+ for match, doc in six.iteritems(docs):
+ print('-->', match)
+ print(doc.strip())
+ print()
+ print()
r = SubprocessLoader(callback)
diff --git a/grc/core/utils/odict.py b/grc/core/utils/odict.py
deleted file mode 100644
index 85927e869f..0000000000
--- a/grc/core/utils/odict.py
+++ /dev/null
@@ -1,115 +0,0 @@
-"""
-Copyright 2008-2015 Free Software Foundation, Inc.
-This file is part of GNU Radio
-
-GNU Radio Companion 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 2
-of the License, or (at your option) any later version.
-
-GNU Radio Companion is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-"""
-
-from UserDict import DictMixin
-
-
-class odict(DictMixin):
-
- def __init__(self, d={}):
- self._keys = list(d.keys())
- self._data = dict(d.copy())
-
- def __setitem__(self, key, value):
- if key not in self._data:
- self._keys.append(key)
- self._data[key] = value
-
- def __getitem__(self, key):
- return self._data[key]
-
- def __delitem__(self, key):
- del self._data[key]
- self._keys.remove(key)
-
- def keys(self):
- return list(self._keys)
-
- def copy(self):
- copy_dict = odict()
- copy_dict._data = self._data.copy()
- copy_dict._keys = list(self._keys)
- return copy_dict
-
- def insert_after(self, pos_key, key, val):
- """
- Insert the new key, value entry after the entry given by the position key.
- If the positional key is None, insert at the end.
-
- Args:
- pos_key: the positional key
- key: the key for the new entry
- val: the value for the new entry
- """
- index = (pos_key is None) and len(self._keys) or self._keys.index(pos_key)
- if key in self._keys:
- raise KeyError('Cannot insert, key "{}" already exists'.format(str(key)))
- self._keys.insert(index+1, key)
- self._data[key] = val
-
- def insert_before(self, pos_key, key, val):
- """
- Insert the new key, value entry before the entry given by the position key.
- If the positional key is None, insert at the begining.
-
- Args:
- pos_key: the positional key
- key: the key for the new entry
- val: the value for the new entry
- """
- index = (pos_key is not None) and self._keys.index(pos_key) or 0
- if key in self._keys:
- raise KeyError('Cannot insert, key "{}" already exists'.format(str(key)))
- self._keys.insert(index, key)
- self._data[key] = val
-
- def find(self, key):
- """
- Get the value for this key if exists.
-
- Args:
- key: the key to search for
-
- Returns:
- the value or None
- """
- if key in self:
- return self[key]
- return None
-
- def findall(self, key):
- """
- Get a list of values for this key.
-
- Args:
- key: the key to search for
-
- Returns:
- a list of values or empty list
- """
- obj = self.find(key)
- if obj is None:
- obj = list()
- if isinstance(obj, list):
- return obj
- return [obj]
-
- def clear(self):
- self._data.clear()
- del self._keys[:] \ No newline at end of file
diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py
index 11e81c4613..25c779b4d2 100644
--- a/grc/gui/ActionHandler.py
+++ b/grc/gui/ActionHandler.py
@@ -18,11 +18,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import gobject
-import gtk
+from __future__ import absolute_import, print_function
+
import os
import subprocess
+from gi.repository import Gtk
+from gi.repository import GObject
+
from . import Dialogs, Preferences, Actions, Executor, Constants
from .FileDialogs import (OpenFlowGraphFileDialog, SaveFlowGraphFileDialog,
SaveConsoleFileDialog, SaveScreenShotDialog,
@@ -33,8 +36,6 @@ from .PropsDialog import PropsDialog
from ..core import ParseXML, Messages
-gobject.threads_init()
-
class ActionHandler:
"""
@@ -60,7 +61,6 @@ class ActionHandler:
self.main_window = MainWindow(platform, self._handle_action)
self.main_window.connect('delete-event', self._quit)
self.main_window.connect('key-press-event', self._handle_key_press)
- self.get_page = self.main_window.get_page
self.get_focus_flag = self.main_window.get_focus_flag
#setup the messages
Messages.register_messenger(self.main_window.add_console_line)
@@ -85,9 +85,8 @@ class ActionHandler:
# prevent key event stealing while the search box is active
# .has_focus() only in newer versions 2.17+?
# .is_focus() seems to work, but exactly the same
- if self.main_window.btwin.search_entry.flags() & gtk.HAS_FOCUS:
+ if self.main_window.btwin.search_entry.has_focus():
return False
- if not self.get_focus_flag(): return False
return Actions.handle_key_press(event)
def _quit(self, window, event):
@@ -105,11 +104,11 @@ class ActionHandler:
def _handle_action(self, action, *args):
#print action
main = self.main_window
- page = main.get_page()
- flow_graph = page.get_flow_graph() if page else None
+ page = main.current_page
+ flow_graph = page.flow_graph if page else None
def flow_graph_update(fg=flow_graph):
- main.vars.update_gui()
+ main.vars.update_gui(fg.blocks)
fg.update()
##################################################
@@ -117,13 +116,13 @@ class ActionHandler:
##################################################
if action == Actions.APPLICATION_INITIALIZE:
if not self.init_file_paths:
- self.init_file_paths = filter(os.path.exists, Preferences.get_open_files())
+ self.init_file_paths = list(filter(os.path.exists, Preferences.get_open_files()))
if not self.init_file_paths: self.init_file_paths = ['']
for file_path in self.init_file_paths:
if file_path: main.new_page(file_path) #load pages from file paths
if Preferences.file_open() in self.init_file_paths:
main.new_page(Preferences.file_open(), show=True)
- if not self.get_page():
+ if not self.main_window.current_page:
main.new_page() # ensure that at least a blank page exists
main.btwin.search_entry.hide()
@@ -159,7 +158,7 @@ class ActionHandler:
self.init = True
elif action == Actions.APPLICATION_QUIT:
if main.close_pages():
- gtk.main_quit()
+ Gtk.main_quit()
exit(0)
##################################################
# Selections
@@ -176,18 +175,18 @@ class ActionHandler:
elif action == Actions.BLOCK_ENABLE:
if flow_graph.enable_selected(True):
flow_graph_update()
- page.get_state_cache().save_new_state(flow_graph.export_data())
- page.set_saved(False)
+ page.state_cache.save_new_state(flow_graph.export_data())
+ page.saved = False
elif action == Actions.BLOCK_DISABLE:
if flow_graph.enable_selected(False):
flow_graph_update()
- page.get_state_cache().save_new_state(flow_graph.export_data())
- page.set_saved(False)
+ page.state_cache.save_new_state(flow_graph.export_data())
+ page.saved = False
elif action == Actions.BLOCK_BYPASS:
if flow_graph.bypass_selected():
flow_graph_update()
- page.get_state_cache().save_new_state(flow_graph.export_data())
- page.set_saved(False)
+ page.state_cache.save_new_state(flow_graph.export_data())
+ page.saved = False
##################################################
# Cut/Copy/Paste
##################################################
@@ -200,8 +199,8 @@ class ActionHandler:
if self.clipboard:
flow_graph.paste_from_clipboard(self.clipboard)
flow_graph_update()
- page.get_state_cache().save_new_state(flow_graph.export_data())
- page.set_saved(False)
+ page.state_cache.save_new_state(flow_graph.export_data())
+ page.saved = False
##################################################
# Create heir block
##################################################
@@ -241,15 +240,15 @@ class ActionHandler:
for connection in block.connections:
# Get id of connected blocks
- source_id = connection.get_source().get_parent().get_id()
- sink_id = connection.get_sink().get_parent().get_id()
+ source_id = connection.source_block.get_id()
+ sink_id = connection.sink_block.get_id()
# If connected block is not in the list of selected blocks create a pad for it
if flow_graph.get_block(source_id) not in flow_graph.get_selected_blocks():
- pads.append({'key': connection.get_sink().get_key(), 'coord': connection.get_source().get_coordinate(), 'block_id' : block.get_id(), 'direction': 'source'})
+ pads.append({'key': connection.sink_port.get_key(), 'coord': connection.source_port.get_coordinate(), 'block_id' : block.get_id(), 'direction': 'source'})
if flow_graph.get_block(sink_id) not in flow_graph.get_selected_blocks():
- pads.append({'key': connection.get_source().get_key(), 'coord': connection.get_sink().get_coordinate(), 'block_id' : block.get_id(), 'direction': 'sink'})
+ pads.append({'key': connection.source_port.get_key(), 'coord': connection.sink_port.get_coordinate(), 'block_id' : block.get_id(), 'direction': 'sink'})
# Copy the selected blocks and paste them into a new page
@@ -334,63 +333,62 @@ class ActionHandler:
# Move/Rotate/Delete/Create
##################################################
elif action == Actions.BLOCK_MOVE:
- page.get_state_cache().save_new_state(flow_graph.export_data())
- page.set_saved(False)
+ page.state_cache.save_new_state(flow_graph.export_data())
+ page.saved = False
elif action in Actions.BLOCK_ALIGNMENTS:
if flow_graph.align_selected(action):
- page.get_state_cache().save_new_state(flow_graph.export_data())
- page.set_saved(False)
+ page.state_cache.save_new_state(flow_graph.export_data())
+ page.saved = False
elif action == Actions.BLOCK_ROTATE_CCW:
if flow_graph.rotate_selected(90):
flow_graph_update()
- page.get_state_cache().save_new_state(flow_graph.export_data())
- page.set_saved(False)
+ page.state_cache.save_new_state(flow_graph.export_data())
+ page.saved = False
elif action == Actions.BLOCK_ROTATE_CW:
if flow_graph.rotate_selected(-90):
flow_graph_update()
- page.get_state_cache().save_new_state(flow_graph.export_data())
- page.set_saved(False)
+ page.state_cache.save_new_state(flow_graph.export_data())
+ page.saved = False
elif action == Actions.ELEMENT_DELETE:
if flow_graph.remove_selected():
flow_graph_update()
- page.get_state_cache().save_new_state(flow_graph.export_data())
+ page.state_cache.save_new_state(flow_graph.export_data())
Actions.NOTHING_SELECT()
- page.set_saved(False)
+ page.saved = False
elif action == Actions.ELEMENT_CREATE:
flow_graph_update()
- page.get_state_cache().save_new_state(flow_graph.export_data())
+ page.state_cache.save_new_state(flow_graph.export_data())
Actions.NOTHING_SELECT()
- page.set_saved(False)
+ page.saved = False
elif action == Actions.BLOCK_INC_TYPE:
if flow_graph.type_controller_modify_selected(1):
flow_graph_update()
- page.get_state_cache().save_new_state(flow_graph.export_data())
- page.set_saved(False)
+ page.state_cache.save_new_state(flow_graph.export_data())
+ page.saved = False
elif action == Actions.BLOCK_DEC_TYPE:
if flow_graph.type_controller_modify_selected(-1):
flow_graph_update()
- page.get_state_cache().save_new_state(flow_graph.export_data())
- page.set_saved(False)
+ page.state_cache.save_new_state(flow_graph.export_data())
+ page.saved = False
elif action == Actions.PORT_CONTROLLER_INC:
if flow_graph.port_controller_modify_selected(1):
flow_graph_update()
- page.get_state_cache().save_new_state(flow_graph.export_data())
- page.set_saved(False)
+ page.state_cache.save_new_state(flow_graph.export_data())
+ page.saved = False
elif action == Actions.PORT_CONTROLLER_DEC:
if flow_graph.port_controller_modify_selected(-1):
flow_graph_update()
- page.get_state_cache().save_new_state(flow_graph.export_data())
- page.set_saved(False)
+ page.state_cache.save_new_state(flow_graph.export_data())
+ page.saved = False
##################################################
# Window stuff
##################################################
elif action == Actions.ABOUT_WINDOW_DISPLAY:
- platform = flow_graph.get_parent()
- Dialogs.AboutDialog(platform.config)
+ Dialogs.AboutDialog(self.platform.config)
elif action == Actions.HELP_WINDOW_DISPLAY:
Dialogs.HelpDialog()
elif action == Actions.TYPES_WINDOW_DISPLAY:
- Dialogs.TypesDialog(flow_graph.get_parent())
+ Dialogs.TypesDialog(self.platform)
elif action == Actions.ERRORS_WINDOW_DISPLAY:
Dialogs.ErrorsDialog(flow_graph)
elif action == Actions.TOGGLE_CONSOLE_WINDOW:
@@ -408,7 +406,7 @@ class ActionHandler:
elif action == Actions.CLEAR_CONSOLE:
main.text_display.clear()
elif action == Actions.SAVE_CONSOLE:
- file_path = SaveConsoleFileDialog(page.get_file_path()).run()
+ file_path = SaveConsoleFileDialog(page.file_path).run()
if file_path is not None:
main.text_display.save(file_path)
elif action == Actions.TOGGLE_HIDE_DISABLED_BLOCKS:
@@ -416,7 +414,7 @@ class ActionHandler:
elif action == Actions.TOGGLE_AUTO_HIDE_PORT_LABELS:
action.save_to_preferences()
for page in main.get_pages():
- page.get_flow_graph().create_shapes()
+ page.flow_graph.create_shapes()
elif action in (Actions.TOGGLE_SNAP_TO_GRID,
Actions.TOGGLE_SHOW_BLOCK_COMMENTS,
Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB):
@@ -424,7 +422,7 @@ class ActionHandler:
elif action == Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY:
action.save_to_preferences()
for page in main.get_pages():
- flow_graph_update(page.get_flow_graph())
+ flow_graph_update(page.flow_graph)
elif action == Actions.TOGGLE_HIDE_VARIABLES:
# Call the variable editor TOGGLE in case it needs to be showing
Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR()
@@ -448,9 +446,9 @@ class ActionHandler:
action.save_to_preferences()
elif action == Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR:
if self.init:
- md = gtk.MessageDialog(main,
- gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO,
- gtk.BUTTONS_CLOSE, "Moving the variable editor requires a restart of GRC.")
+ md = Gtk.MessageDialog(main,
+ Gtk.DIALOG_DESTROY_WITH_PARENT, Gtk.MESSAGE_INFO,
+ Gtk.BUTTONS_CLOSE, "Moving the variable editor requires a restart of GRC.")
md.run()
md.destroy()
action.save_to_preferences()
@@ -463,33 +461,33 @@ class ActionHandler:
else:
selected_block = flow_graph.get_selected_block()
if selected_block:
- self.dialog = PropsDialog(selected_block)
- response = gtk.RESPONSE_APPLY
- while response == gtk.RESPONSE_APPLY: # rerun the dialog if Apply was hit
+ self.dialog = PropsDialog(self.main_window, selected_block)
+ response = Gtk.ResponseType.APPLY
+ while response == Gtk.ResponseType.APPLY: # rerun the dialog if Apply was hit
response = self.dialog.run()
- if response in (gtk.RESPONSE_APPLY, gtk.RESPONSE_ACCEPT):
+ if response in (Gtk.ResponseType.APPLY, Gtk.ResponseType.ACCEPT):
flow_graph_update()
- page.get_state_cache().save_new_state(flow_graph.export_data())
- page.set_saved(False)
+ page.state_cache.save_new_state(flow_graph.export_data())
+ page.saved = False
else: # restore the current state
- n = page.get_state_cache().get_current_state()
+ n = page.state_cache.get_current_state()
flow_graph.import_data(n)
flow_graph_update()
- if response == gtk.RESPONSE_APPLY:
+ if response == Gtk.ResponseType.APPLY:
# null action, that updates the main window
Actions.ELEMENT_SELECT()
self.dialog.destroy()
self.dialog = None
elif action == Actions.EXTERNAL_UPDATE:
- page.get_state_cache().save_new_state(flow_graph.export_data())
+ page.state_cache.save_new_state(flow_graph.export_data())
flow_graph_update()
if self.dialog is not None:
self.dialog.update_gui(force=True)
- page.set_saved(False)
+ page.saved = False
elif action == Actions.VARIABLE_EDITOR_UPDATE:
- page.get_state_cache().save_new_state(flow_graph.export_data())
+ page.state_cache.save_new_state(flow_graph.export_data())
flow_graph_update()
- page.set_saved(False)
+ page.saved = False
##################################################
# View Parser Errors
##################################################
@@ -499,19 +497,19 @@ class ActionHandler:
# Undo/Redo
##################################################
elif action == Actions.FLOW_GRAPH_UNDO:
- n = page.get_state_cache().get_prev_state()
+ n = page.state_cache.get_prev_state()
if n:
flow_graph.unselect()
flow_graph.import_data(n)
flow_graph_update()
- page.set_saved(False)
+ page.saved = False
elif action == Actions.FLOW_GRAPH_REDO:
- n = page.get_state_cache().get_next_state()
+ n = page.state_cache.get_next_state()
if n:
flow_graph.unselect()
flow_graph.import_data(n)
flow_graph_update()
- page.set_saved(False)
+ page.saved = False
##################################################
# New/Open/Save/Close
##################################################
@@ -521,15 +519,18 @@ class ActionHandler:
flow_graph._options_block.get_param('generate_options').set_value(args[0])
flow_graph_update()
elif action == Actions.FLOW_GRAPH_OPEN:
- file_paths = args if args else OpenFlowGraphFileDialog(page.get_file_path()).run()
- if file_paths: #open a new page for each file, show only the first
+ # TODO: Disable opening the flowgraph
+ pass
+ '''
+ file_paths = args if args else OpenFlowGraphFileDialog(page.file_path).run()
+ if file_paths: # Open a new page for each file, show only the first
for i,file_path in enumerate(file_paths):
main.new_page(file_path, show=(i==0))
Preferences.add_recent_file(file_path)
main.tool_bar.refresh_submenus()
main.menu_bar.refresh_submenus()
main.vars.update_gui()
-
+ '''
elif action == Actions.FLOW_GRAPH_OPEN_QSS_THEME:
file_paths = OpenQSSFileDialog(self.platform.config.install_prefix +
'/share/gnuradio/themes/').run()
@@ -544,27 +545,27 @@ class ActionHandler:
main.close_page()
elif action == Actions.FLOW_GRAPH_SAVE:
#read-only or undefined file path, do save-as
- if page.get_read_only() or not page.get_file_path():
+ if page.get_read_only() or not page.file_path:
Actions.FLOW_GRAPH_SAVE_AS()
#otherwise try to save
else:
try:
- ParseXML.to_file(flow_graph.export_data(), page.get_file_path())
- flow_graph.grc_file_path = page.get_file_path()
- page.set_saved(True)
+ ParseXML.to_file(flow_graph.export_data(), page.file_path)
+ flow_graph.grc_file_path = page.file_path
+ page.saved = True
except IOError:
- Messages.send_fail_save(page.get_file_path())
- page.set_saved(False)
+ Messages.send_fail_save(page.file_path)
+ page.saved = False
elif action == Actions.FLOW_GRAPH_SAVE_AS:
- file_path = SaveFlowGraphFileDialog(page.get_file_path()).run()
+ file_path = SaveFlowGraphFileDialog(page.file_path).run()
if file_path is not None:
- page.set_file_path(file_path)
+ page.file_path = os.path.abspath(file_path)
Actions.FLOW_GRAPH_SAVE()
Preferences.add_recent_file(file_path)
main.tool_bar.refresh_submenus()
main.menu_bar.refresh_submenus()
elif action == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
- file_path, background_transparent = SaveScreenShotDialog(page.get_file_path()).run()
+ file_path, background_transparent = SaveScreenShotDialog(page.file_path).run()
if file_path is not None:
pixbuf = flow_graph.get_drawing_area().get_screenshot(background_transparent)
pixbuf.save(file_path, Constants.IMAGE_FILE_EXTENSION[1:])
@@ -572,38 +573,38 @@ class ActionHandler:
# Gen/Exec/Stop
##################################################
elif action == Actions.FLOW_GRAPH_GEN:
- if not page.get_proc():
- if not page.get_saved() or not page.get_file_path():
- Actions.FLOW_GRAPH_SAVE() #only save if file path missing or not saved
- if page.get_saved() and page.get_file_path():
+ if not page.process:
+ if not page.saved or not page.file_path:
+ Actions.FLOW_GRAPH_SAVE() # only save if file path missing or not saved
+ if page.saved and page.file_path:
generator = page.get_generator()
try:
- Messages.send_start_gen(generator.get_file_path())
+ Messages.send_start_gen(generator.file_path)
generator.write()
except Exception as e:
Messages.send_fail_gen(e)
else:
self.generator = None
elif action == Actions.FLOW_GRAPH_EXEC:
- if not page.get_proc():
+ if not page.process:
Actions.FLOW_GRAPH_GEN()
xterm = self.platform.config.xterm_executable
if Preferences.xterm_missing() != xterm:
if not os.path.exists(xterm):
Dialogs.MissingXTermDialog(xterm)
Preferences.xterm_missing(xterm)
- if page.get_saved() and page.get_file_path():
+ if page.saved and page.file_path:
Executor.ExecFlowGraphThread(
flow_graph_page=page,
xterm_executable=xterm,
callback=self.update_exec_stop
)
elif action == Actions.FLOW_GRAPH_KILL:
- if page.get_proc():
+ if page.process:
try:
- page.get_proc().kill()
+ page.process.kill()
except:
- print "could not kill process: %d" % page.get_proc().pid
+ print("could not kill process: %d" % page.process.pid)
elif action == Actions.PAGE_CHANGE: # pass and run the global actions
pass
elif action == Actions.RELOAD_BLOCKS:
@@ -644,12 +645,12 @@ class ActionHandler:
shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
else:
- print '!!! Action "%s" not handled !!!' % action
+ print('!!! Action "%s" not handled !!!' % action)
##################################################
# Global Actions for all States
##################################################
- page = main.get_page() # page and flowgraph might have changed
- flow_graph = page.get_flow_graph() if page else None
+ page = main.current_page # page and flow graph might have changed
+ flow_graph = page.flow_graph if page else None
selected_blocks = flow_graph.get_selected_blocks()
selected_block = selected_blocks[0] if selected_blocks else None
@@ -674,7 +675,7 @@ class ActionHandler:
can_disable = any(block.get_state() != Constants.BLOCK_DISABLED
for block in selected_blocks)
can_bypass_all = all(block.can_bypass() for block in selected_blocks) \
- and any (not block.get_bypassed() for block in selected_blocks)
+ and any(not block.get_bypassed() for block in selected_blocks)
Actions.BLOCK_ENABLE.set_sensitive(can_enable)
Actions.BLOCK_DISABLE.set_sensitive(can_disable)
Actions.BLOCK_BYPASS.set_sensitive(can_bypass_all)
@@ -685,28 +686,24 @@ class ActionHandler:
Actions.BUSSIFY_SINKS.set_sensitive(bool(selected_blocks))
Actions.RELOAD_BLOCKS.set_sensitive(True)
Actions.FIND_BLOCKS.set_sensitive(True)
- #set the exec and stop buttons
+
self.update_exec_stop()
- #saved status
- Actions.FLOW_GRAPH_SAVE.set_sensitive(not page.get_saved())
+
+ Actions.FLOW_GRAPH_SAVE.set_sensitive(not page.saved)
main.update()
- try: #set the size of the flow graph area (if changed)
- new_size = (flow_graph.get_option('window_size') or
- self.platform.config.default_canvas_size)
- if flow_graph.get_size() != tuple(new_size):
- flow_graph.set_size(*new_size)
- except: pass
- #draw the flow graph
+
flow_graph.update_selected()
flow_graph.queue_draw()
- return True #action was handled
+
+ return True # action was handled
def update_exec_stop(self):
"""
Update the exec and stop buttons.
Lock and unlock the mutex for race conditions with exec flow graph threads.
"""
- sensitive = self.main_window.get_flow_graph().is_valid() and not self.get_page().get_proc()
+ page = self.main_window.current_page
+ sensitive = page.flow_graph.is_valid() and not page.process
Actions.FLOW_GRAPH_GEN.set_sensitive(sensitive)
Actions.FLOW_GRAPH_EXEC.set_sensitive(sensitive)
- Actions.FLOW_GRAPH_KILL.set_sensitive(self.get_page().get_proc() is not None)
+ Actions.FLOW_GRAPH_KILL.set_sensitive(page.process is not None)
diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py
index 9b2af36b93..3a51e80918 100644
--- a/grc/gui/Actions.py
+++ b/grc/gui/Actions.py
@@ -17,19 +17,26 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import pygtk
-pygtk.require('2.0')
-import gtk
+from __future__ import absolute_import
-import Preferences
+import six
+
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import GObject
+
+from . import Preferences
NO_MODS_MASK = 0
+
########################################################################
# Actions API
########################################################################
_actions_keypress_dict = dict()
-_keymap = gtk.gdk.keymap_get_default()
+_keymap = Gdk.Keymap.get_default()
_used_mods_mask = NO_MODS_MASK
@@ -44,12 +51,11 @@ def handle_key_press(event):
Returns:
true if handled
"""
- _used_mods_mask = reduce(lambda x, y: x | y, [mod_mask for keyval, mod_mask in _actions_keypress_dict], NO_MODS_MASK)
# extract the key value and the consumed modifiers
- keyval, egroup, level, consumed = _keymap.translate_keyboard_state(
- event.hardware_keycode, event.state, event.group)
+ _unused, keyval, egroup, level, consumed = _keymap.translate_keyboard_state(
+ event.hardware_keycode, event.get_state(), event.group)
# get the modifier mask and ignore irrelevant modifiers
- mod_mask = event.state & ~consumed & _used_mods_mask
+ mod_mask = event.get_state() & ~consumed & _used_mods_mask
# look up the keypress and call the action
try:
_actions_keypress_dict[(keyval, mod_mask)]()
@@ -64,7 +70,7 @@ _all_actions_list = list()
def get_all_actions():
return _all_actions_list
-_accel_group = gtk.AccelGroup()
+_accel_group = Gtk.AccelGroup()
def get_accel_group():
@@ -77,13 +83,16 @@ class _ActionBase(object):
Register actions and keypresses with this module.
"""
def __init__(self, label, keypresses):
+ global _used_mods_mask
+
_all_actions_list.append(self)
for i in range(len(keypresses)/2):
keyval, mod_mask = keypresses[i*2:(i+1)*2]
# register this keypress
- if _actions_keypress_dict.has_key((keyval, mod_mask)):
+ if (keyval, mod_mask) in _actions_keypress_dict:
raise KeyError('keyval/mod_mask pair already registered "%s"' % str((keyval, mod_mask)))
_actions_keypress_dict[(keyval, mod_mask)] = self
+ _used_mods_mask |= mod_mask
# set the accelerator group, and accelerator path
# register the key name and mod mask with the accelerator path
if label is None:
@@ -91,7 +100,7 @@ class _ActionBase(object):
accel_path = '<main>/' + self.get_name()
self.set_accel_group(get_accel_group())
self.set_accel_path(accel_path)
- gtk.accel_map_add_entry(accel_path, keyval, mod_mask)
+ Gtk.AccelMap.add_entry(accel_path, keyval, mod_mask)
self.args = None
def __str__(self):
@@ -99,7 +108,7 @@ class _ActionBase(object):
The string representation should be the name of the action id.
Try to find the action id for this action by searching this module.
"""
- for name, value in globals().iteritems():
+ for name, value in six.iteritems(globals()):
if value == self:
return name
return self.get_name()
@@ -114,9 +123,9 @@ class _ActionBase(object):
self.emit('activate')
-class Action(gtk.Action, _ActionBase):
+class Action(Gtk.Action, _ActionBase):
"""
- A custom Action class based on gtk.Action.
+ A custom Action class based on Gtk.Action.
Pass additional arguments such as keypresses.
"""
@@ -127,18 +136,18 @@ class Action(gtk.Action, _ActionBase):
Args:
key_presses: a tuple of (keyval1, mod_mask1, keyval2, mod_mask2, ...)
- the: regular gtk.Action parameters (defaults to None)
+ the: regular Gtk.Action parameters (defaults to None)
"""
if name is None:
name = label
- gtk.Action.__init__(self, name=name, label=label, tooltip=tooltip,
+ GObject.GObject.__init__(self, name=name, label=label, tooltip=tooltip,
stock_id=stock_id)
_ActionBase.__init__(self, label, keypresses)
-class ToggleAction(gtk.ToggleAction, _ActionBase):
+class ToggleAction(Gtk.ToggleAction, _ActionBase):
"""
- A custom Action class based on gtk.ToggleAction.
+ A custom Action class based on Gtk.ToggleAction.
Pass additional arguments such as keypresses.
"""
@@ -149,11 +158,11 @@ class ToggleAction(gtk.ToggleAction, _ActionBase):
Args:
key_presses: a tuple of (keyval1, mod_mask1, keyval2, mod_mask2, ...)
- the: regular gtk.Action parameters (defaults to None)
+ the: regular Gtk.Action parameters (defaults to None)
"""
if name is None:
name = label
- gtk.ToggleAction.__init__(self, name=name, label=label,
+ GObject.GObject.__init__(self, name=name, label=label,
tooltip=tooltip, stock_id=stock_id)
_ActionBase.__init__(self, label, keypresses)
self.preference_name = preference_name
@@ -177,114 +186,114 @@ VARIABLE_EDITOR_UPDATE = Action()
FLOW_GRAPH_NEW = Action(
label='_New',
tooltip='Create a new flow graph',
- stock_id=gtk.STOCK_NEW,
- keypresses=(gtk.keysyms.n, gtk.gdk.CONTROL_MASK),
+ stock_id=Gtk.STOCK_NEW,
+ keypresses=(Gdk.KEY_n, Gdk.ModifierType.CONTROL_MASK),
)
FLOW_GRAPH_OPEN = Action(
label='_Open',
tooltip='Open an existing flow graph',
- stock_id=gtk.STOCK_OPEN,
- keypresses=(gtk.keysyms.o, gtk.gdk.CONTROL_MASK),
+ stock_id=Gtk.STOCK_OPEN,
+ keypresses=(Gdk.KEY_o, Gdk.ModifierType.CONTROL_MASK),
)
FLOW_GRAPH_OPEN_RECENT = Action(
label='Open _Recent',
tooltip='Open a recently used flow graph',
- stock_id=gtk.STOCK_OPEN,
+ stock_id=Gtk.STOCK_OPEN,
)
FLOW_GRAPH_SAVE = Action(
label='_Save',
tooltip='Save the current flow graph',
- stock_id=gtk.STOCK_SAVE,
- keypresses=(gtk.keysyms.s, gtk.gdk.CONTROL_MASK),
+ stock_id=Gtk.STOCK_SAVE,
+ keypresses=(Gdk.KEY_s, Gdk.ModifierType.CONTROL_MASK),
)
FLOW_GRAPH_SAVE_AS = Action(
label='Save _As',
tooltip='Save the current flow graph as...',
- stock_id=gtk.STOCK_SAVE_AS,
- keypresses=(gtk.keysyms.s, gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK),
+ stock_id=Gtk.STOCK_SAVE_AS,
+ keypresses=(Gdk.KEY_s, Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK),
)
FLOW_GRAPH_CLOSE = Action(
label='_Close',
tooltip='Close the current flow graph',
- stock_id=gtk.STOCK_CLOSE,
- keypresses=(gtk.keysyms.w, gtk.gdk.CONTROL_MASK),
+ stock_id=Gtk.STOCK_CLOSE,
+ keypresses=(Gdk.KEY_w, Gdk.ModifierType.CONTROL_MASK),
)
APPLICATION_INITIALIZE = Action()
APPLICATION_QUIT = Action(
label='_Quit',
tooltip='Quit program',
- stock_id=gtk.STOCK_QUIT,
- keypresses=(gtk.keysyms.q, gtk.gdk.CONTROL_MASK),
+ stock_id=Gtk.STOCK_QUIT,
+ keypresses=(Gdk.KEY_q, Gdk.ModifierType.CONTROL_MASK),
)
FLOW_GRAPH_UNDO = Action(
label='_Undo',
tooltip='Undo a change to the flow graph',
- stock_id=gtk.STOCK_UNDO,
- keypresses=(gtk.keysyms.z, gtk.gdk.CONTROL_MASK),
+ stock_id=Gtk.STOCK_UNDO,
+ keypresses=(Gdk.KEY_z, Gdk.ModifierType.CONTROL_MASK),
)
FLOW_GRAPH_REDO = Action(
label='_Redo',
tooltip='Redo a change to the flow graph',
- stock_id=gtk.STOCK_REDO,
- keypresses=(gtk.keysyms.y, gtk.gdk.CONTROL_MASK),
+ stock_id=Gtk.STOCK_REDO,
+ keypresses=(Gdk.KEY_y, Gdk.ModifierType.CONTROL_MASK),
)
NOTHING_SELECT = Action()
SELECT_ALL = Action(
label='Select _All',
tooltip='Select all blocks and connections in the flow graph',
- stock_id=gtk.STOCK_SELECT_ALL,
- keypresses=(gtk.keysyms.a, gtk.gdk.CONTROL_MASK),
+ stock_id=Gtk.STOCK_SELECT_ALL,
+ keypresses=(Gdk.KEY_a, Gdk.ModifierType.CONTROL_MASK),
)
ELEMENT_SELECT = Action()
ELEMENT_CREATE = Action()
ELEMENT_DELETE = Action(
label='_Delete',
tooltip='Delete the selected blocks',
- stock_id=gtk.STOCK_DELETE,
- keypresses=(gtk.keysyms.Delete, NO_MODS_MASK),
+ stock_id=Gtk.STOCK_DELETE,
+ keypresses=(Gdk.KEY_Delete, NO_MODS_MASK),
)
BLOCK_MOVE = Action()
BLOCK_ROTATE_CCW = Action(
label='Rotate Counterclockwise',
tooltip='Rotate the selected blocks 90 degrees to the left',
- stock_id=gtk.STOCK_GO_BACK,
- keypresses=(gtk.keysyms.Left, NO_MODS_MASK),
+ stock_id=Gtk.STOCK_GO_BACK,
+ keypresses=(Gdk.KEY_Left, NO_MODS_MASK),
)
BLOCK_ROTATE_CW = Action(
label='Rotate Clockwise',
tooltip='Rotate the selected blocks 90 degrees to the right',
- stock_id=gtk.STOCK_GO_FORWARD,
- keypresses=(gtk.keysyms.Right, NO_MODS_MASK),
+ stock_id=Gtk.STOCK_GO_FORWARD,
+ keypresses=(Gdk.KEY_Right, NO_MODS_MASK),
)
BLOCK_VALIGN_TOP = Action(
label='Vertical Align Top',
tooltip='Align tops of selected blocks',
- keypresses=(gtk.keysyms.t, gtk.gdk.SHIFT_MASK),
+ keypresses=(Gdk.KEY_t, Gdk.ModifierType.SHIFT_MASK),
)
BLOCK_VALIGN_MIDDLE = Action(
label='Vertical Align Middle',
tooltip='Align centers of selected blocks vertically',
- keypresses=(gtk.keysyms.m, gtk.gdk.SHIFT_MASK),
+ keypresses=(Gdk.KEY_m, Gdk.ModifierType.SHIFT_MASK),
)
BLOCK_VALIGN_BOTTOM = Action(
label='Vertical Align Bottom',
tooltip='Align bottoms of selected blocks',
- keypresses=(gtk.keysyms.b, gtk.gdk.SHIFT_MASK),
+ keypresses=(Gdk.KEY_b, Gdk.ModifierType.SHIFT_MASK),
)
BLOCK_HALIGN_LEFT = Action(
label='Horizontal Align Left',
tooltip='Align left edges of blocks selected blocks',
- keypresses=(gtk.keysyms.l, gtk.gdk.SHIFT_MASK),
+ keypresses=(Gdk.KEY_l, Gdk.ModifierType.SHIFT_MASK),
)
BLOCK_HALIGN_CENTER = Action(
label='Horizontal Align Center',
tooltip='Align centers of selected blocks horizontally',
- keypresses=(gtk.keysyms.c, gtk.gdk.SHIFT_MASK),
+ keypresses=(Gdk.KEY_c, Gdk.ModifierType.SHIFT_MASK),
)
BLOCK_HALIGN_RIGHT = Action(
label='Horizontal Align Right',
tooltip='Align right edges of selected blocks',
- keypresses=(gtk.keysyms.r, gtk.gdk.SHIFT_MASK),
+ keypresses=(Gdk.KEY_r, Gdk.ModifierType.SHIFT_MASK),
)
BLOCK_ALIGNMENTS = [
BLOCK_VALIGN_TOP,
@@ -298,26 +307,26 @@ BLOCK_ALIGNMENTS = [
BLOCK_PARAM_MODIFY = Action(
label='_Properties',
tooltip='Modify params for the selected block',
- stock_id=gtk.STOCK_PROPERTIES,
- keypresses=(gtk.keysyms.Return, NO_MODS_MASK),
+ stock_id=Gtk.STOCK_PROPERTIES,
+ keypresses=(Gdk.KEY_Return, NO_MODS_MASK),
)
BLOCK_ENABLE = Action(
label='E_nable',
tooltip='Enable the selected blocks',
- stock_id=gtk.STOCK_CONNECT,
- keypresses=(gtk.keysyms.e, NO_MODS_MASK),
+ stock_id=Gtk.STOCK_CONNECT,
+ keypresses=(Gdk.KEY_e, NO_MODS_MASK),
)
BLOCK_DISABLE = Action(
label='D_isable',
tooltip='Disable the selected blocks',
- stock_id=gtk.STOCK_DISCONNECT,
- keypresses=(gtk.keysyms.d, NO_MODS_MASK),
+ stock_id=Gtk.STOCK_DISCONNECT,
+ keypresses=(Gdk.KEY_d, NO_MODS_MASK),
)
BLOCK_BYPASS = Action(
label='_Bypass',
tooltip='Bypass the selected block',
- stock_id=gtk.STOCK_MEDIA_FORWARD,
- keypresses=(gtk.keysyms.b, NO_MODS_MASK),
+ stock_id=Gtk.STOCK_MEDIA_FORWARD,
+ keypresses=(Gdk.KEY_b, NO_MODS_MASK),
)
TOGGLE_SNAP_TO_GRID = ToggleAction(
label='_Snap to grid',
@@ -327,8 +336,8 @@ TOGGLE_SNAP_TO_GRID = ToggleAction(
TOGGLE_HIDE_DISABLED_BLOCKS = ToggleAction(
label='Hide _Disabled Blocks',
tooltip='Toggle visibility of disabled blocks and connections',
- stock_id=gtk.STOCK_MISSING_IMAGE,
- keypresses=(gtk.keysyms.d, gtk.gdk.CONTROL_MASK),
+ stock_id=Gtk.STOCK_MISSING_IMAGE,
+ keypresses=(Gdk.KEY_d, Gdk.ModifierType.CONTROL_MASK),
)
TOGGLE_HIDE_VARIABLES = ToggleAction(
label='Hide Variables',
@@ -339,9 +348,9 @@ TOGGLE_HIDE_VARIABLES = ToggleAction(
TOGGLE_FLOW_GRAPH_VAR_EDITOR = ToggleAction(
label='Show _Variable Editor',
tooltip='Show the variable editor. Modify variables and imports in this flow graph',
- stock_id=gtk.STOCK_EDIT,
+ stock_id=Gtk.STOCK_EDIT,
default=True,
- keypresses=(gtk.keysyms.e, gtk.gdk.CONTROL_MASK),
+ keypresses=(Gdk.KEY_e, Gdk.ModifierType.CONTROL_MASK),
preference_name='variable_editor_visable',
)
TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR = ToggleAction(
@@ -376,42 +385,42 @@ TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY = ToggleAction(
BLOCK_CREATE_HIER = Action(
label='C_reate Hier',
tooltip='Create hier block from selected blocks',
- stock_id=gtk.STOCK_CONNECT,
-# keypresses=(gtk.keysyms.c, NO_MODS_MASK),
+ stock_id=Gtk.STOCK_CONNECT,
+# keypresses=(Gdk.KEY_c, NO_MODS_MASK),
)
BLOCK_CUT = Action(
label='Cu_t',
tooltip='Cut',
- stock_id=gtk.STOCK_CUT,
- keypresses=(gtk.keysyms.x, gtk.gdk.CONTROL_MASK),
+ stock_id=Gtk.STOCK_CUT,
+ keypresses=(Gdk.KEY_x, Gdk.ModifierType.CONTROL_MASK),
)
BLOCK_COPY = Action(
label='_Copy',
tooltip='Copy',
- stock_id=gtk.STOCK_COPY,
- keypresses=(gtk.keysyms.c, gtk.gdk.CONTROL_MASK),
+ stock_id=Gtk.STOCK_COPY,
+ keypresses=(Gdk.KEY_c, Gdk.ModifierType.CONTROL_MASK),
)
BLOCK_PASTE = Action(
label='_Paste',
tooltip='Paste',
- stock_id=gtk.STOCK_PASTE,
- keypresses=(gtk.keysyms.v, gtk.gdk.CONTROL_MASK),
+ stock_id=Gtk.STOCK_PASTE,
+ keypresses=(Gdk.KEY_v, Gdk.ModifierType.CONTROL_MASK),
)
ERRORS_WINDOW_DISPLAY = Action(
label='Flowgraph _Errors',
tooltip='View flow graph errors',
- stock_id=gtk.STOCK_DIALOG_ERROR,
+ stock_id=Gtk.STOCK_DIALOG_ERROR,
)
TOGGLE_CONSOLE_WINDOW = ToggleAction(
label='Show _Console Panel',
tooltip='Toggle visibility of the console',
- keypresses=(gtk.keysyms.r, gtk.gdk.CONTROL_MASK),
+ keypresses=(Gdk.KEY_r, Gdk.ModifierType.CONTROL_MASK),
preference_name='console_window_visible'
)
TOGGLE_BLOCKS_WINDOW = ToggleAction(
label='Show _Block Tree Panel',
tooltip='Toggle visibility of the block tree widget',
- keypresses=(gtk.keysyms.b, gtk.gdk.CONTROL_MASK),
+ keypresses=(Gdk.KEY_b, Gdk.ModifierType.CONTROL_MASK),
preference_name='blocks_window_visible'
)
TOGGLE_SCROLL_LOCK = ToggleAction(
@@ -422,106 +431,106 @@ TOGGLE_SCROLL_LOCK = ToggleAction(
ABOUT_WINDOW_DISPLAY = Action(
label='_About',
tooltip='About this program',
- stock_id=gtk.STOCK_ABOUT,
+ stock_id=Gtk.STOCK_ABOUT,
)
HELP_WINDOW_DISPLAY = Action(
label='_Help',
tooltip='Usage tips',
- stock_id=gtk.STOCK_HELP,
- keypresses=(gtk.keysyms.F1, NO_MODS_MASK),
+ stock_id=Gtk.STOCK_HELP,
+ keypresses=(Gdk.KEY_F1, NO_MODS_MASK),
)
TYPES_WINDOW_DISPLAY = Action(
label='_Types',
tooltip='Types color mapping',
- stock_id=gtk.STOCK_DIALOG_INFO,
+ stock_id=Gtk.STOCK_DIALOG_INFO,
)
FLOW_GRAPH_GEN = Action(
label='_Generate',
tooltip='Generate the flow graph',
- stock_id=gtk.STOCK_CONVERT,
- keypresses=(gtk.keysyms.F5, NO_MODS_MASK),
+ stock_id=Gtk.STOCK_CONVERT,
+ keypresses=(Gdk.KEY_F5, NO_MODS_MASK),
)
FLOW_GRAPH_EXEC = Action(
label='_Execute',
tooltip='Execute the flow graph',
- stock_id=gtk.STOCK_MEDIA_PLAY,
- keypresses=(gtk.keysyms.F6, NO_MODS_MASK),
+ stock_id=Gtk.STOCK_MEDIA_PLAY,
+ keypresses=(Gdk.KEY_F6, NO_MODS_MASK),
)
FLOW_GRAPH_KILL = Action(
label='_Kill',
tooltip='Kill the flow graph',
- stock_id=gtk.STOCK_STOP,
- keypresses=(gtk.keysyms.F7, NO_MODS_MASK),
+ stock_id=Gtk.STOCK_STOP,
+ keypresses=(Gdk.KEY_F7, NO_MODS_MASK),
)
FLOW_GRAPH_SCREEN_CAPTURE = Action(
label='Screen Ca_pture',
tooltip='Create a screen capture of the flow graph',
- stock_id=gtk.STOCK_PRINT,
- keypresses=(gtk.keysyms.Print, NO_MODS_MASK),
+ stock_id=Gtk.STOCK_PRINT,
+ keypresses=(Gdk.KEY_Print, NO_MODS_MASK),
)
PORT_CONTROLLER_DEC = Action(
- keypresses=(gtk.keysyms.minus, NO_MODS_MASK, gtk.keysyms.KP_Subtract, NO_MODS_MASK),
+ keypresses=(Gdk.KEY_minus, NO_MODS_MASK, Gdk.KEY_KP_Subtract, NO_MODS_MASK),
)
PORT_CONTROLLER_INC = Action(
- keypresses=(gtk.keysyms.plus, NO_MODS_MASK, gtk.keysyms.KP_Add, NO_MODS_MASK),
+ keypresses=(Gdk.KEY_plus, NO_MODS_MASK, Gdk.KEY_KP_Add, NO_MODS_MASK),
)
BLOCK_INC_TYPE = Action(
- keypresses=(gtk.keysyms.Down, NO_MODS_MASK),
+ keypresses=(Gdk.KEY_Down, NO_MODS_MASK),
)
BLOCK_DEC_TYPE = Action(
- keypresses=(gtk.keysyms.Up, NO_MODS_MASK),
+ keypresses=(Gdk.KEY_Up, NO_MODS_MASK),
)
RELOAD_BLOCKS = Action(
label='Reload _Blocks',
tooltip='Reload Blocks',
- stock_id=gtk.STOCK_REFRESH
+ stock_id=Gtk.STOCK_REFRESH
)
FIND_BLOCKS = Action(
label='_Find Blocks',
tooltip='Search for a block by name (and key)',
- stock_id=gtk.STOCK_FIND,
- keypresses=(gtk.keysyms.f, gtk.gdk.CONTROL_MASK,
- gtk.keysyms.slash, NO_MODS_MASK),
+ stock_id=Gtk.STOCK_FIND,
+ keypresses=(Gdk.KEY_f, Gdk.ModifierType.CONTROL_MASK,
+ Gdk.KEY_slash, NO_MODS_MASK),
)
CLEAR_CONSOLE = Action(
label='_Clear Console',
tooltip='Clear Console',
- stock_id=gtk.STOCK_CLEAR,
+ stock_id=Gtk.STOCK_CLEAR,
)
SAVE_CONSOLE = Action(
label='_Save Console',
tooltip='Save Console',
- stock_id=gtk.STOCK_SAVE,
+ stock_id=Gtk.STOCK_SAVE,
)
OPEN_HIER = Action(
label='Open H_ier',
tooltip='Open the source of the selected hierarchical block',
- stock_id=gtk.STOCK_JUMP_TO,
+ stock_id=Gtk.STOCK_JUMP_TO,
)
BUSSIFY_SOURCES = Action(
label='Toggle So_urce Bus',
tooltip='Gang source ports into a single bus port',
- stock_id=gtk.STOCK_JUMP_TO,
+ stock_id=Gtk.STOCK_JUMP_TO,
)
BUSSIFY_SINKS = Action(
label='Toggle S_ink Bus',
tooltip='Gang sink ports into a single bus port',
- stock_id=gtk.STOCK_JUMP_TO,
+ stock_id=Gtk.STOCK_JUMP_TO,
)
XML_PARSER_ERRORS_DISPLAY = Action(
label='_Parser Errors',
tooltip='View errors that occurred while parsing XML files',
- stock_id=gtk.STOCK_DIALOG_ERROR,
+ stock_id=Gtk.STOCK_DIALOG_ERROR,
)
FLOW_GRAPH_OPEN_QSS_THEME = Action(
label='Set Default QT GUI _Theme',
tooltip='Set a default QT Style Sheet file to use for QT GUI',
- stock_id=gtk.STOCK_OPEN,
+ stock_id=Gtk.STOCK_OPEN,
)
TOOLS_RUN_FDESIGN = Action(
label='Filter Design Tool',
tooltip='Execute gr_filter_design',
- stock_id=gtk.STOCK_EXECUTE,
+ stock_id=Gtk.STOCK_EXECUTE,
)
TOOLS_MORE_TO_COME = Action(
label='More to come',
diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py
index a4819b973c..0c18836c4e 100644
--- a/grc/gui/Bars.py
+++ b/grc/gui/Bars.py
@@ -17,9 +17,11 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import pygtk
-pygtk.require('2.0')
-import gtk
+from __future__ import absolute_import
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+from gi.repository import GObject
from . import Actions
@@ -60,9 +62,10 @@ TOOLBAR_LIST = (
Actions.OPEN_HIER,
)
+
# The list of actions and categories for the menu bar.
MENU_BAR_LIST = (
- (gtk.Action('File', '_File', None, None), [
+ (Gtk.Action(name='File', label='_File'), [
'flow_graph_new',
Actions.FLOW_GRAPH_OPEN,
'flow_graph_recent',
@@ -75,7 +78,7 @@ MENU_BAR_LIST = (
Actions.FLOW_GRAPH_CLOSE,
Actions.APPLICATION_QUIT,
]),
- (gtk.Action('Edit', '_Edit', None, None), [
+ (Gtk.Action(name='Edit', label='_Edit'), [
Actions.FLOW_GRAPH_UNDO,
Actions.FLOW_GRAPH_REDO,
None,
@@ -87,7 +90,7 @@ MENU_BAR_LIST = (
None,
Actions.BLOCK_ROTATE_CCW,
Actions.BLOCK_ROTATE_CW,
- (gtk.Action('Align', '_Align', None, None), Actions.BLOCK_ALIGNMENTS),
+ (Gtk.Action('Align', '_Align', None, None), Actions.BLOCK_ALIGNMENTS),
None,
Actions.BLOCK_ENABLE,
Actions.BLOCK_DISABLE,
@@ -95,7 +98,7 @@ MENU_BAR_LIST = (
None,
Actions.BLOCK_PARAM_MODIFY,
]),
- (gtk.Action('View', '_View', None, None), [
+ (Gtk.Action(name='View', label='_View'), [
Actions.TOGGLE_BLOCKS_WINDOW,
None,
Actions.TOGGLE_CONSOLE_WINDOW,
@@ -117,12 +120,12 @@ MENU_BAR_LIST = (
Actions.ERRORS_WINDOW_DISPLAY,
Actions.FIND_BLOCKS,
]),
- (gtk.Action('Run', '_Run', None, None), [
+ (Gtk.Action(name='Run', label='_Run'), [
Actions.FLOW_GRAPH_GEN,
Actions.FLOW_GRAPH_EXEC,
Actions.FLOW_GRAPH_KILL,
]),
- (gtk.Action('Tools', '_Tools', None, None), [
+ (Gtk.Action(name='Tools', label='_Tools'), [
Actions.TOOLS_RUN_FDESIGN,
Actions.FLOW_GRAPH_OPEN_QSS_THEME,
None,
@@ -130,7 +133,7 @@ MENU_BAR_LIST = (
None,
Actions.TOOLS_MORE_TO_COME,
]),
- (gtk.Action('Help', '_Help', None, None), [
+ (Gtk.Action(name='Help', label='_Help'), [
Actions.HELP_WINDOW_DISPLAY,
Actions.TYPES_WINDOW_DISPLAY,
Actions.XML_PARSER_ERRORS_DISPLAY,
@@ -139,6 +142,7 @@ MENU_BAR_LIST = (
]),
)
+
# The list of actions for the context menu.
CONTEXT_MENU_LIST = [
Actions.BLOCK_CUT,
@@ -152,7 +156,7 @@ CONTEXT_MENU_LIST = [
Actions.BLOCK_DISABLE,
Actions.BLOCK_BYPASS,
None,
- (gtk.Action('More', '_More', None, None), [
+ (Gtk.Action(name='More', label='_More'), [
Actions.BLOCK_CREATE_HIER,
Actions.OPEN_HIER,
None,
@@ -189,13 +193,13 @@ class SubMenuCreator(object):
def _fill_flow_graph_new_submenu(self, action):
"""Sub menu to create flow-graph with pre-set generate mode"""
- menu = gtk.Menu()
+ menu = Gtk.Menu()
for key, name, default in self.generate_modes:
if default:
item = Actions.FLOW_GRAPH_NEW.create_menu_item()
item.set_label(name)
else:
- item = gtk.MenuItem(name, use_underline=False)
+ item = Gtk.MenuItem(name=name, use_underline=False)
item.connect('activate', self.callback_adaptor, (action, key))
menu.append(item)
menu.show_all()
@@ -203,12 +207,12 @@ class SubMenuCreator(object):
def _fill_flow_graph_recent_submenu(self, action):
"""menu showing recent flow-graphs"""
- import Preferences
- menu = gtk.Menu()
+ from . import Preferences
+ menu = Gtk.Menu()
recent_files = Preferences.get_recent_files()
if len(recent_files) > 0:
for i, file_name in enumerate(recent_files):
- item = gtk.MenuItem("%d. %s" % (i+1, file_name), use_underline=False)
+ item = Gtk.MenuItem(name="%d. %s" % (i+1, file_name), use_underline=False)
item.connect('activate', self.callback_adaptor,
(action, file_name))
menu.append(item)
@@ -217,7 +221,7 @@ class SubMenuCreator(object):
return None
-class Toolbar(gtk.Toolbar, SubMenuCreator):
+class Toolbar(Gtk.Toolbar, SubMenuCreator):
"""The gtk toolbar with actions added from the toolbar list."""
def __init__(self, generate_modes, action_handler_callback):
@@ -226,23 +230,25 @@ class Toolbar(gtk.Toolbar, SubMenuCreator):
Look up the action for each name in the action list and add it to the
toolbar.
"""
- gtk.Toolbar.__init__(self)
- self.set_style(gtk.TOOLBAR_ICONS)
+ GObject.GObject.__init__(self)
+ self.set_style(Gtk.ToolbarStyle.ICONS)
SubMenuCreator.__init__(self, generate_modes, action_handler_callback)
for action in TOOLBAR_LIST:
if isinstance(action, tuple) and isinstance(action[1], str):
# create a button with a sub-menu
- action[0].set_tool_item_type(gtk.MenuToolButton)
+ # TODO: Fix later
+ #action[0].set_tool_item_type(Gtk.MenuToolButton)
item = action[0].create_tool_item()
- self.create_submenu(action, item)
- self.refresh_submenus()
+ #self.create_submenu(action, item)
+ #self.refresh_submenus()
elif action is None:
- item = gtk.SeparatorToolItem()
+ item = Gtk.SeparatorToolItem()
else:
- action.set_tool_item_type(gtk.ToolButton)
+ #TODO: Fix later
+ #action.set_tool_item_type(Gtk.ToolButton)
item = action.create_tool_item()
# this reset of the tooltip property is required
# (after creating the tool item) for the tooltip to show
@@ -255,14 +261,14 @@ class MenuHelperMixin(object):
def _fill_menu(self, actions, menu=None):
"""Create a menu from list of actions"""
- menu = menu or gtk.Menu()
+ menu = menu or Gtk.Menu()
for item in actions:
if isinstance(item, tuple):
menu_item = self._make_sub_menu(*item)
elif isinstance(item, str):
menu_item = getattr(self, 'create_' + item)()
elif item is None:
- menu_item = gtk.SeparatorMenuItem()
+ menu_item = Gtk.SeparatorMenuItem()
else:
menu_item = item.create_menu_item()
menu.append(menu_item)
@@ -276,7 +282,7 @@ class MenuHelperMixin(object):
return main
-class MenuBar(gtk.MenuBar, MenuHelperMixin, SubMenuCreator):
+class MenuBar(Gtk.MenuBar, MenuHelperMixin, SubMenuCreator):
"""The gtk menu bar with actions added from the menu bar list."""
def __init__(self, generate_modes, action_handler_callback):
@@ -286,13 +292,13 @@ class MenuBar(gtk.MenuBar, MenuHelperMixin, SubMenuCreator):
Look up the action for each name in the action list and add it to the
submenu. Add the submenu to the menu bar.
"""
- gtk.MenuBar.__init__(self)
+ GObject.GObject.__init__(self)
SubMenuCreator.__init__(self, generate_modes, action_handler_callback)
for main_action, actions in MENU_BAR_LIST:
self.append(self._make_sub_menu(main_action, actions))
def create_flow_graph_new(self):
- main = gtk.ImageMenuItem(gtk.STOCK_NEW)
+ main = Gtk.ImageMenuItem(Gtk.STOCK_NEW)
main.set_label(Actions.FLOW_GRAPH_NEW.get_label())
func = self._fill_flow_graph_new_submenu
self.submenus.append((Actions.FLOW_GRAPH_NEW, func, main))
@@ -300,7 +306,7 @@ class MenuBar(gtk.MenuBar, MenuHelperMixin, SubMenuCreator):
return main
def create_flow_graph_recent(self):
- main = gtk.ImageMenuItem(gtk.STOCK_OPEN)
+ main = Gtk.ImageMenuItem(Gtk.STOCK_OPEN)
main.set_label(Actions.FLOW_GRAPH_OPEN_RECENT.get_label())
func = self._fill_flow_graph_recent_submenu
self.submenus.append((Actions.FLOW_GRAPH_OPEN, func, main))
@@ -310,9 +316,9 @@ class MenuBar(gtk.MenuBar, MenuHelperMixin, SubMenuCreator):
return main
-class ContextMenu(gtk.Menu, MenuHelperMixin):
+class ContextMenu(Gtk.Menu, MenuHelperMixin):
"""The gtk menu with actions added from the context menu list."""
def __init__(self):
- gtk.Menu.__init__(self)
+ GObject.GObject.__init__(self)
self._fill_menu(CONTEXT_MENU_LIST, self)
diff --git a/grc/gui/Block.py b/grc/gui/Block.py
index 55c8805fae..49bba4f3db 100644
--- a/grc/gui/Block.py
+++ b/grc/gui/Block.py
@@ -17,37 +17,24 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import pygtk
-pygtk.require('2.0')
-import gtk
-import pango
-
-from . import Actions, Colors, Utils, Constants
-
+from __future__ import absolute_import
+import math
+import gi
+gi.require_version('Gtk', '3.0')
+gi.require_version('PangoCairo', '1.0')
+from gi.repository import Gtk, Pango, PangoCairo
+
+from . import Actions, Colors, Utils
+
+from .Constants import (
+ BLOCK_LABEL_PADDING, PORT_SPACING, PORT_SEPARATION, LABEL_SEPARATION,
+ PORT_BORDER_SEPARATION, POSSIBLE_ROTATIONS, BLOCK_FONT, PARAM_FONT
+)
from . Element import Element
from ..core.Param import num_to_str
-from ..core.utils import odict
from ..core.utils.complexity import calculate_flowgraph_complexity
from ..core.Block import Block as _Block
-BLOCK_MARKUP_TMPL="""\
-#set $foreground = $block.is_valid() and 'black' or 'red'
-<span foreground="$foreground" font_desc="$font"><b>$encode($block.get_name())</b></span>"""
-
-# Includes the additional complexity markup if enabled
-COMMENT_COMPLEXITY_MARKUP_TMPL="""\
-#set $foreground = $block.get_enabled() and '#444' or '#888'
-#if $complexity
-<span foreground="#444" size="medium" font_desc="$font"><b>$encode($complexity)</b></span>#slurp
-#end if
-#if $complexity and $comment
-<span></span>
-#end if
-#if $comment
-<span foreground="$foreground" font_desc="$font">$encode($comment)</span>#slurp
-#end if
-"""
-
class Block(Element, _Block):
"""The graphical signal block."""
@@ -58,33 +45,17 @@ class Block(Element, _Block):
Add graphics related params to the block.
"""
_Block.__init__(self, flow_graph, n)
+ self.W = self.H = 0
+ self._add_param(key='_coordinate', name='GUI Coordinate', value='(0, 0)',
+ hide='all')
+ self._add_param(key='_rotation', name='GUI Rotation', value='0',
+ hide='all')
- self.W = 0
- self.H = 0
- #add the position param
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({
- 'name': 'GUI Coordinate',
- 'key': '_coordinate',
- 'type': 'raw',
- 'value': '(0, 0)',
- 'hide': 'all',
- })
- ))
- self.get_params().append(self.get_parent().get_parent().Param(
- block=self,
- n=odict({
- 'name': 'GUI Rotation',
- 'key': '_rotation',
- 'type': 'raw',
- 'value': '0',
- 'hide': 'all',
- })
- ))
- Element.__init__(self)
+ Element.__init__(self) # needs the params
self._comment_pixmap = None
+ self._bg_color = Colors.BLOCK_ENABLED_COLOR
self.has_busses = [False, False] # source, sink
+ self.layouts = []
def get_coordinate(self):
"""
@@ -93,23 +64,13 @@ class Block(Element, _Block):
Returns:
the coordinate tuple (x, y) or (0, 0) if failure
"""
- proximity = Constants.BORDER_PROXIMITY_SENSITIVITY
- try: #should evaluate to tuple
- coor = eval(self.get_param('_coordinate').get_value())
- x, y = map(int, coor)
- fgW,fgH = self.get_parent().get_size()
- if x <= 0:
- x = 0
- elif x >= fgW - proximity:
- x = fgW - proximity
- if y <= 0:
- y = 0
- elif y >= fgH - proximity:
- y = fgH - proximity
- return (x, y)
+ try:
+ coor = self.get_param('_coordinate').get_value() # should evaluate to tuple
+ coor = tuple(int(x) for x in coor[1:-1].split(','))
except:
- self.set_coordinate((0, 0))
- return (0, 0)
+ coor = 0, 0
+ self.set_coordinate(coor)
+ return coor
def set_coordinate(self, coor):
"""
@@ -124,41 +85,7 @@ class Block(Element, _Block):
Utils.align_to_grid(coor[0] + offset_x) - offset_x,
Utils.align_to_grid(coor[1] + offset_y) - offset_y
)
- self.get_param('_coordinate').set_value(str(coor))
-
- def bound_move_delta(self, delta_coor):
- """
- Limit potential moves from exceeding the bounds of the canvas
-
- Args:
- delta_coor: requested delta coordinate (dX, dY) to move
-
- Returns:
- The delta coordinate possible to move while keeping the block on the canvas
- or the input (dX, dY) on failure
- """
- dX, dY = delta_coor
-
- try:
- fgW, fgH = self.get_parent().get_size()
- x, y = map(int, eval(self.get_param("_coordinate").get_value()))
- if self.is_horizontal():
- sW, sH = self.W, self.H
- else:
- sW, sH = self.H, self.W
-
- if x + dX < 0:
- dX = -x
- elif dX + x + sW >= fgW:
- dX = fgW - x - sW
- if y + dY < 0:
- dY = -y
- elif dY + y + sH >= fgH:
- dY = fgH - y - sH
- except:
- pass
-
- return ( dX, dY )
+ self.get_param('_coordinate').set_value(repr(coor))
def get_rotation(self):
"""
@@ -168,11 +95,11 @@ class Block(Element, _Block):
the rotation in degrees or 0 if failure
"""
try: #should evaluate to dict
- rotation = eval(self.get_param('_rotation').get_value())
- return int(rotation)
+ rotation = int(self.get_param('_rotation').get_value())
except:
- self.set_rotation(Constants.POSSIBLE_ROTATIONS[0])
- return Constants.POSSIBLE_ROTATIONS[0]
+ rotation = POSSIBLE_ROTATIONS[0]
+ self.set_rotation(rotation)
+ return rotation
def set_rotation(self, rot):
"""
@@ -181,7 +108,7 @@ class Block(Element, _Block):
Args:
rot: the rotation in degrees
"""
- self.get_param('_rotation').set_value(str(rot))
+ self.get_param('_rotation').set_value(repr(int(rot)))
def create_shapes(self):
"""Update the block, parameters, and ports when a change occurs."""
@@ -192,71 +119,53 @@ class Block(Element, _Block):
def create_labels(self):
"""Create the labels for the signal block."""
Element.create_labels(self)
- self._bg_color = self.is_dummy_block and Colors.MISSING_BLOCK_BACKGROUND_COLOR or \
- self.get_bypassed() and Colors.BLOCK_BYPASSED_COLOR or \
- self.get_enabled() and Colors.BLOCK_ENABLED_COLOR or Colors.BLOCK_DISABLED_COLOR
-
- layouts = list()
+ self._bg_color = Colors.MISSING_BLOCK_BACKGROUND_COLOR if self.is_dummy_block else \
+ Colors.BLOCK_BYPASSED_COLOR if self.get_bypassed() else \
+ Colors.BLOCK_ENABLED_COLOR if self.get_enabled() else \
+ Colors.BLOCK_DISABLED_COLOR
+ del self.layouts[:]
#create the main layout
- layout = gtk.DrawingArea().create_pango_layout('')
- layouts.append(layout)
- layout.set_markup(Utils.parse_template(BLOCK_MARKUP_TMPL, block=self, font=Constants.BLOCK_FONT))
+ layout = Gtk.DrawingArea().create_pango_layout('')
+ layout.set_markup('<span foreground="{foreground}" font_desc="{font}"><b>{name}</b></span>'.format(
+ foreground='black' if self.is_valid() else 'red', font=BLOCK_FONT, name=Utils.encode(self.get_name())
+ ))
self.label_width, self.label_height = layout.get_pixel_size()
+ self.layouts.append(layout)
#display the params
if self.is_dummy_block:
- markups = [
- '<span foreground="black" font_desc="{font}"><b>key: </b>{key}</span>'
- ''.format(font=Constants.PARAM_FONT, key=self._key)
- ]
+ markups = ['<span foreground="black" font_desc="{font}"><b>key: </b>{key}</span>'.format(
+ font=PARAM_FONT, key=self._key
+ )]
else:
- markups = [param.get_markup() for param in self.get_params() if param.get_hide() not in ('all', 'part')]
+ markups = [param.format_block_surface_markup() for param in self.get_params() if param.get_hide() not in ('all', 'part')]
if markups:
- layout = gtk.DrawingArea().create_pango_layout('')
- layout.set_spacing(Constants.LABEL_SEPARATION * pango.SCALE)
+ layout = Gtk.DrawingArea().create_pango_layout('')
+ layout.set_spacing(LABEL_SEPARATION*Pango.SCALE)
layout.set_markup('\n'.join(markups))
- layouts.append(layout)
+ self.layouts.append(layout)
w, h = layout.get_pixel_size()
self.label_width = max(w, self.label_width)
- self.label_height += h + Constants.LABEL_SEPARATION
- width = self.label_width
- height = self.label_height
- #setup the pixmap
- pixmap = self.get_parent().new_pixmap(width, height)
- gc = pixmap.new_gc()
- gc.set_foreground(self._bg_color)
- pixmap.draw_rectangle(gc, True, 0, 0, width, height)
- #draw the layouts
- h_off = 0
- for i,layout in enumerate(layouts):
- w,h = layout.get_pixel_size()
- if i == 0: w_off = (width-w)/2
- else: w_off = 0
- pixmap.draw_layout(gc, w_off, h_off, layout)
- h_off = h + h_off + Constants.LABEL_SEPARATION
- #create vertical and horizontal pixmaps
- self.horizontal_label = pixmap
- if self.is_vertical():
- self.vertical_label = self.get_parent().new_pixmap(height, width)
- Utils.rotate_pixmap(gc, self.horizontal_label, self.vertical_label)
+ self.label_height += h + LABEL_SEPARATION
+
#calculate width and height needed
- W = self.label_width + 2 * Constants.BLOCK_LABEL_PADDING
+ W = self.label_width + 2 * BLOCK_LABEL_PADDING
def get_min_height_for_ports():
- visible_ports = filter(lambda p: not p.get_hide(), ports)
- min_height = 2*Constants.PORT_BORDER_SEPARATION + len(visible_ports) * Constants.PORT_SEPARATION
+ visible_ports = [p for p in ports if not p.get_hide()]
+ min_height = 2*PORT_BORDER_SEPARATION + len(visible_ports) * PORT_SEPARATION
if visible_ports:
min_height -= ports[0].H
return min_height
H = max(
[ # labels
- self.label_height + 2 * Constants.BLOCK_LABEL_PADDING
+ self.label_height + 2 * BLOCK_LABEL_PADDING
] +
[ # ports
get_min_height_for_ports() for ports in (self.get_sources_gui(), self.get_sinks_gui())
] +
[ # bus ports only
- 2 * Constants.PORT_BORDER_SEPARATION +
- sum([port.H + Constants.PORT_SPACING for port in ports if port.get_type() == 'bus']) - Constants.PORT_SPACING
+ 2 * PORT_BORDER_SEPARATION +
+ sum([port.H + PORT_SPACING for port in ports if port.get_type() == 'bus']) - PORT_SPACING
for ports in (self.get_sources_gui(), self.get_sinks_gui())
]
)
@@ -268,26 +177,31 @@ class Block(Element, _Block):
self.create_comment_label()
def create_comment_label(self):
- comment = self.get_comment() # Returns None if there are no comments
- complexity = None
+ markups = []
- # Show the flowgraph complexity on the top block if enabled
+ # Show the flow graph complexity on the top block if enabled
if Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY.get_active() and self.get_key() == "options":
- complexity = calculate_flowgraph_complexity(self.get_parent())
- complexity = "Complexity: {}bal".format(num_to_str(complexity))
+ complexity = calculate_flowgraph_complexity(self.parent)
+ markups.append(
+ '<span foreground="#444" size="medium" font_desc="{font}">'
+ '<b>Complexity: {num}bal</b></span>'.format(num=num_to_str(complexity), font=BLOCK_FONT)
+ )
+ comment = self.get_comment() # Returns None if there are no comments
+ if comment:
+ if markups:
+ markups.append('<span></span>')
- layout = gtk.DrawingArea().create_pango_layout('')
- layout.set_markup(Utils.parse_template(COMMENT_COMPLEXITY_MARKUP_TMPL,
- block=self,
- comment=comment,
- complexity=complexity,
- font=Constants.BLOCK_FONT))
+ markups.append('<span foreground="{foreground}" font_desc="{font}">{comment}</span>'.format(
+ foreground='#444' if self.get_enabled() else '#888', font=BLOCK_FONT, comment=Utils.encode(comment)
+ ))
+ layout = Gtk.DrawingArea().create_pango_layout('')
+ layout.set_markup(''.join(markups))
# Setup the pixel map. Make sure that layout not empty
width, height = layout.get_pixel_size()
if width and height:
- padding = Constants.BLOCK_LABEL_PADDING
- pixmap = self.get_parent().new_pixmap(width + 2 * padding,
+ padding = BLOCK_LABEL_PADDING
+ pixmap = self.parent.new_pixmap(width + 2 * padding,
height + 2 * padding)
gc = pixmap.new_gc()
gc.set_foreground(Colors.COMMENT_BACKGROUND_COLOR)
@@ -298,29 +212,50 @@ class Block(Element, _Block):
else:
self._comment_pixmap = None
- def draw(self, gc, window):
+ def draw(self, widget, cr):
"""
Draw the signal block with label and inputs/outputs.
-
- Args:
- gc: the graphics context
- window: the gtk window to draw on
"""
# draw ports
for port in self.get_ports_gui():
- port.draw(gc, window)
+ port.draw(widget, cr)
# draw main block
- x, y = self.get_coordinate()
- Element.draw(
- self, gc, window, bg_color=self._bg_color,
- border_color=self.is_highlighted() and Colors.HIGHLIGHT_COLOR or
- self.is_dummy_block and Colors.MISSING_BLOCK_BORDER_COLOR or Colors.BORDER_COLOR,
+ border_color = (
+ Colors.HIGHLIGHT_COLOR if self.is_highlighted() else
+ Colors.MISSING_BLOCK_BORDER_COLOR if self.is_dummy_block else
+ Colors.BORDER_COLOR
)
- #draw label image
+ Element.draw(self, widget, cr, border_color, self._bg_color)
+ x, y = self.get_coordinate()
+ # create the image surface
+ width = self.label_width
+ height = self.label_height
+ cr.set_source_rgb(*self._bg_color)
+ cr.save()
if self.is_horizontal():
- window.draw_drawable(gc, self.horizontal_label, 0, 0, x+Constants.BLOCK_LABEL_PADDING, y+(self.H-self.label_height)/2, -1, -1)
+ cr.translate(x + BLOCK_LABEL_PADDING, y + (self.H - self.label_height) / 2)
elif self.is_vertical():
- window.draw_drawable(gc, self.vertical_label, 0, 0, x+(self.H-self.label_height)/2, y+Constants.BLOCK_LABEL_PADDING, -1, -1)
+ cr.translate(x + (self.H - self.label_height) / 2, y + BLOCK_LABEL_PADDING)
+ cr.rotate(-90 * math.pi / 180.)
+ cr.translate(-width, 0)
+
+ # cr.rectangle(0, 0, width, height)
+ # cr.fill()
+
+ # draw the layouts
+ h_off = 0
+ for i, layout in enumerate(self.layouts):
+ w, h = layout.get_pixel_size()
+ if i == 0:
+ w_off = (width - w) / 2
+ else:
+ w_off = 0
+ cr.translate(w_off, h_off)
+ PangoCairo.update_layout(cr, layout)
+ PangoCairo.show_layout(cr, layout)
+ cr.translate(-w_off, -h_off)
+ h_off = h + h_off + LABEL_SEPARATION
+ cr.restore()
def what_is_selected(self, coor, coor_m=None):
"""
@@ -344,8 +279,8 @@ class Block(Element, _Block):
x, y = self.get_coordinate()
if self.is_horizontal():
- y += self.H + Constants.BLOCK_LABEL_PADDING
+ y += self.H + BLOCK_LABEL_PADDING
else:
- x += self.H + Constants.BLOCK_LABEL_PADDING
+ x += self.H + BLOCK_LABEL_PADDING
window.draw_drawable(gc, self._comment_pixmap, 0, 0, x, y, -1, -1)
diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py
index f49eb6c4fe..89aac53a0e 100644
--- a/grc/gui/BlockTreeWindow.py
+++ b/grc/gui/BlockTreeWindow.py
@@ -17,71 +17,61 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import pygtk
-pygtk.require('2.0')
-import gtk
-import gobject
+from __future__ import absolute_import
+import six
+
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import GObject
from . import Actions, Utils
from . import Constants
-NAME_INDEX = 0
-KEY_INDEX = 1
-DOC_INDEX = 2
-
-DOC_MARKUP_TMPL = """\
-#set $docs = []
-#if $doc.get('')
- #set $docs += $doc.pop('').splitlines() + ['']
-#end if
-#for b, d in $doc.iteritems()
- #set $docs += ['--- {0} ---'.format(b)] + d.splitlines() + ['']
-#end for
-#set $len_out = 0
-#for $n, $line in $enumerate($docs[:-1])
-#if $n
-
-#end if
-$encode($line)#slurp
-#set $len_out += $len($line)
-#if $n > 10 or $len_out > 500
-
-...#slurp
-#break
-#end if
-#end for
-#if $len_out == 0
-undocumented#slurp
-#end if"""
-
-CAT_MARKUP_TMPL = """
-#set $name = $cat[-1]
-#if len($cat) > 1
-Category: $cat[-1]
-##
-#elif $name == 'Core'
-Module: Core
-
-This subtree is meant for blocks included with GNU Radio (in-tree).
-##
-#elif $name == $default_module
-This subtree holds all blocks (from OOT modules) that specify no module name. \
-The module name is the root category enclosed in square brackets.
-
-Please consider contacting OOT module maintainer for any block in here \
-and kindly ask to update their GRC Block Descriptions or Block Tree to include a module name.
-#else
-Module: $name
-##
-#end if
-""".strip()
-
-
-class BlockTreeWindow(gtk.VBox):
+NAME_INDEX, KEY_INDEX, DOC_INDEX = range(3)
+
+
+def _format_doc(doc):
+ docs = []
+ if doc.get(''):
+ docs += doc.pop('').splitlines() + ['']
+ for block_name, docstring in six.iteritems(doc):
+ docs.append('--- {0} ---'.format(block_name))
+ docs += docstring.splitlines()
+ docs.append('')
+ out = ''
+ for n, line in enumerate(docs[:-1]):
+ if n:
+ out += '\n'
+ out += Utils.encode(line)
+ if n > 10 or len(out) > 500:
+ out += '\n...'
+ break
+ return out or 'undocumented'
+
+
+def _format_cat_tooltip(category):
+ tooltip = '{}: {}'.format('Category' if len(category) > 1 else 'Module', category[-1])
+
+ if category == ('Core',):
+ tooltip += '\n\nThis subtree is meant for blocks included with GNU Radio (in-tree).'
+
+ elif category == (Constants.DEFAULT_BLOCK_MODULE_NAME,):
+ tooltip += '\n\n' + Constants.DEFAULT_BLOCK_MODULE_TOOLTIP
+
+ return tooltip
+
+
+class BlockTreeWindow(Gtk.VBox):
"""The block selection panel."""
- def __init__(self, platform, get_flow_graph):
+ __gsignals__ = {
+ 'create_new_block': (GObject.SIGNAL_RUN_FIRST, None, (str,))
+ }
+
+ def __init__(self, platform):
"""
BlockTreeWindow constructor.
Create a tree view of the possible blocks in the platform.
@@ -90,58 +80,52 @@ class BlockTreeWindow(gtk.VBox):
Args:
platform: the particular platform will all block prototypes
- get_flow_graph: get the selected flow graph
"""
- gtk.VBox.__init__(self)
+ Gtk.VBox.__init__(self)
self.platform = platform
- self.get_flow_graph = get_flow_graph
# search entry
- self.search_entry = gtk.Entry()
+ self.search_entry = Gtk.Entry()
try:
- self.search_entry.set_icon_from_stock(gtk.ENTRY_ICON_PRIMARY, gtk.STOCK_FIND)
- self.search_entry.set_icon_activatable(gtk.ENTRY_ICON_PRIMARY, False)
- self.search_entry.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, gtk.STOCK_CLOSE)
+ self.search_entry.set_icon_from_stock(Gtk.EntryIconPosition.PRIMARY, Gtk.STOCK_FIND)
+ self.search_entry.set_icon_activatable(Gtk.EntryIconPosition.PRIMARY, False)
+ self.search_entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, Gtk.STOCK_CLOSE)
self.search_entry.connect('icon-release', self._handle_icon_event)
except AttributeError:
pass # no icon for old pygtk
self.search_entry.connect('changed', self._update_search_tree)
self.search_entry.connect('key-press-event', self._handle_search_key_press)
- self.pack_start(self.search_entry, False)
+ self.pack_start(self.search_entry, False, False, 0)
# make the tree model for holding blocks and a temporary one for search results
- self.treestore = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
- self.treestore_search = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
+ self.treestore = Gtk.TreeStore(GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING)
+ self.treestore_search = Gtk.TreeStore(GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING)
- self.treeview = gtk.TreeView(self.treestore)
+ self.treeview = Gtk.TreeView(self.treestore)
self.treeview.set_enable_search(False) # disable pop up search box
self.treeview.set_search_column(-1) # really disable search
self.treeview.set_headers_visible(False)
- self.treeview.add_events(gtk.gdk.BUTTON_PRESS_MASK)
+ self.treeview.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.treeview.connect('button-press-event', self._handle_mouse_button_press)
self.treeview.connect('key-press-event', self._handle_search_key_press)
- self.treeview.get_selection().set_mode('single')
- renderer = gtk.CellRendererText()
- column = gtk.TreeViewColumn('Blocks', renderer, text=NAME_INDEX)
+ self.treeview.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
+ renderer = Gtk.CellRendererText()
+ column = Gtk.TreeViewColumn('Blocks', renderer, text=NAME_INDEX)
self.treeview.append_column(column)
- # try to enable the tooltips (available in pygtk 2.12 and above)
- try:
- self.treeview.set_tooltip_column(DOC_INDEX)
- except:
- pass
+ self.treeview.set_tooltip_column(DOC_INDEX)
# setup sort order
column.set_sort_column_id(0)
- self.treestore.set_sort_column_id(0, gtk.SORT_ASCENDING)
+ self.treestore.set_sort_column_id(0, Gtk.SortType.ASCENDING)
# setup drag and drop
- self.treeview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, Constants.DND_TARGETS, gtk.gdk.ACTION_COPY)
+ self.treeview.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, Constants.DND_TARGETS, Gdk.DragAction.COPY)
self.treeview.connect('drag-data-get', self._handle_drag_get_data)
# make the scrolled window to hold the tree view
- scrolled_window = gtk.ScrolledWindow()
- scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled_window = Gtk.ScrolledWindow()
+ scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scrolled_window.add_with_viewport(self.treeview)
scrolled_window.set_size_request(Constants.DEFAULT_BLOCKS_WINDOW_WIDTH, -1)
- self.pack_start(scrolled_window)
+ self.pack_start(scrolled_window, True, True, 0)
# map categories to iters, automatic mapping for root
self._categories = {tuple(): None}
self._categories_search = {tuple(): None}
@@ -154,7 +138,7 @@ class BlockTreeWindow(gtk.VBox):
def repopulate(self):
self.clear()
- for block in self.platform.blocks.itervalues():
+ for block in six.itervalues(self.platform.blocks):
if block.category:
self.add_block(block)
self.expand_module_in_tree()
@@ -188,15 +172,13 @@ class BlockTreeWindow(gtk.VBox):
iter_ = treestore.insert_before(categories[parent_category[:-1]], None)
treestore.set_value(iter_, NAME_INDEX, parent_cat_name)
treestore.set_value(iter_, KEY_INDEX, '')
- treestore.set_value(iter_, DOC_INDEX, Utils.parse_template(
- CAT_MARKUP_TMPL, cat=parent_category, default_module=Constants.DEFAULT_BLOCK_MODULE_NAME))
+ treestore.set_value(iter_, DOC_INDEX, _format_cat_tooltip(parent_cat_name))
categories[parent_category] = iter_
-
# add block
iter_ = treestore.insert_before(categories[category], None)
treestore.set_value(iter_, NAME_INDEX, block.get_name())
treestore.set_value(iter_, KEY_INDEX, block.get_key())
- treestore.set_value(iter_, DOC_INDEX, Utils.parse_template(DOC_MARKUP_TMPL, doc=block.get_doc()))
+ treestore.set_value(iter_, DOC_INDEX, _format_doc(block.get_doc()))
def update_docs(self):
"""Update the documentation column of every block"""
@@ -206,8 +188,7 @@ class BlockTreeWindow(gtk.VBox):
return # category node, no doc string
key = model.get_value(iter_, KEY_INDEX)
block = self.platform.blocks[key]
- doc = Utils.parse_template(DOC_MARKUP_TMPL, doc=block.get_doc())
- model.set_value(iter_, DOC_INDEX, doc)
+ model.set_value(iter_, DOC_INDEX, _format_doc(block.get_doc()))
self.treestore.foreach(update_doc)
self.treestore_search.foreach(update_doc)
@@ -226,16 +207,6 @@ class BlockTreeWindow(gtk.VBox):
treestore, iter = selection.get_selected()
return iter and treestore.get_value(iter, KEY_INDEX) or ''
- def _add_selected_block(self):
- """
- Add the selected block with the given key to the flow graph.
- """
- key = self._get_selected_block_key()
- if key:
- self.get_flow_graph().add_new_block(key)
- return True
- return False
-
def _expand_category(self):
treestore, iter = self.treeview.get_selection().get_selected()
if iter and treestore.iter_has_child(iter):
@@ -246,9 +217,9 @@ class BlockTreeWindow(gtk.VBox):
## Event Handlers
############################################################
def _handle_icon_event(self, widget, icon, event):
- if icon == gtk.ENTRY_ICON_PRIMARY:
+ if icon == Gtk.EntryIconPosition.PRIMARY:
pass
- elif icon == gtk.ENTRY_ICON_SECONDARY:
+ elif icon == Gtk.EntryIconPosition.SECONDARY:
widget.set_text('')
self.search_entry.hide()
@@ -258,8 +229,8 @@ class BlockTreeWindow(gtk.VBox):
self.treeview.set_model(self.treestore)
self.expand_module_in_tree()
else:
- matching_blocks = filter(lambda b: key in b.get_key().lower() or key in b.get_name().lower(),
- self.platform.blocks.values())
+ matching_blocks = [b for b in list(self.platform.blocks.values())
+ if key in b.get_key().lower() or key in b.get_name().lower()]
self.treestore_search.clear()
self._categories_search = {tuple(): None}
@@ -270,7 +241,7 @@ class BlockTreeWindow(gtk.VBox):
def _handle_search_key_press(self, widget, event):
"""Handle Return and Escape key events in search entry and treeview"""
- if event.keyval == gtk.keysyms.Return:
+ if event.keyval == Gdk.KEY_Return:
# add block on enter
if widget == self.search_entry:
@@ -280,24 +251,28 @@ class BlockTreeWindow(gtk.VBox):
selected = self.treestore_search.iter_children(selected)
if selected is not None:
key = self.treestore_search.get_value(selected, KEY_INDEX)
- if key: self.get_flow_graph().add_new_block(key)
+ if key: self.emit('create_new_block', key)
elif widget == self.treeview:
- self._add_selected_block() or self._expand_category()
+ key = self._get_selected_block_key()
+ if key:
+ self.emit('create_new_block', key)
+ else:
+ self._expand_category()
else:
return False # propagate event
- elif event.keyval == gtk.keysyms.Escape:
+ elif event.keyval == Gdk.KEY_Escape:
# reset the search
self.search_entry.set_text('')
self.search_entry.hide()
- elif (event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.f) \
- or event.keyval == gtk.keysyms.slash:
+ elif (event.get_state() & Gdk.ModifierType.CONTROL_MASK and event.keyval == Gdk.KEY_f) \
+ or event.keyval == Gdk.KEY_slash:
# propagation doesn't work although treeview search is disabled =(
# manually trigger action...
Actions.FIND_BLOCKS.activate()
- elif event.state & gtk.gdk.CONTROL_MASK and event.keyval == gtk.keysyms.b:
+ elif event.get_state() & Gdk.ModifierType.CONTROL_MASK and event.keyval == Gdk.KEY_b:
# ugly...
Actions.TOGGLE_BLOCKS_WINDOW.activate()
@@ -320,5 +295,7 @@ class BlockTreeWindow(gtk.VBox):
Handle the mouse button press.
If a left double click is detected, call add selected block.
"""
- if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
- self._add_selected_block()
+ if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
+ key = self._get_selected_block_key()
+ if key:
+ self.emit('create_new_block', key)
diff --git a/grc/gui/Colors.py b/grc/gui/Colors.py
index d322afa410..6ee31e5e18 100644
--- a/grc/gui/Colors.py
+++ b/grc/gui/Colors.py
@@ -16,35 +16,90 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-try:
- import pygtk
- pygtk.require('2.0')
- import gtk
-
- _COLORMAP = gtk.gdk.colormap_get_system() #create all of the colors
- def get_color(color_code): return _COLORMAP.alloc_color(color_code, True, True)
-
- HIGHLIGHT_COLOR = get_color('#00FFFF')
- BORDER_COLOR = get_color('#444444')
- # missing blocks stuff
- MISSING_BLOCK_BACKGROUND_COLOR = get_color('#FFF2F2')
- MISSING_BLOCK_BORDER_COLOR = get_color('red')
- #param entry boxes
- PARAM_ENTRY_TEXT_COLOR = get_color('black')
- ENTRYENUM_CUSTOM_COLOR = get_color('#EEEEEE')
- #flow graph color constants
- FLOWGRAPH_BACKGROUND_COLOR = get_color('#FFFFFF')
- COMMENT_BACKGROUND_COLOR = get_color('#F3F3F3')
- FLOWGRAPH_EDGE_COLOR = COMMENT_BACKGROUND_COLOR
- #block color constants
- BLOCK_ENABLED_COLOR = get_color('#F1ECFF')
- BLOCK_DISABLED_COLOR = get_color('#CCCCCC')
- BLOCK_BYPASSED_COLOR = get_color('#F4FF81')
- #connection color constants
- CONNECTION_ENABLED_COLOR = get_color('black')
- CONNECTION_DISABLED_COLOR = get_color('#BBBBBB')
- CONNECTION_ERROR_COLOR = get_color('red')
-except:
- print 'Unable to import Colors'
-
-DEFAULT_DOMAIN_COLOR_CODE = '#777777'
+
+from __future__ import absolute_import
+
+
+def get_color(color_code):
+ chars_per_color = 2 if len(color_code) > 4 else 1
+ offsets = range(1, 3 * chars_per_color + 1, chars_per_color)
+ return tuple(int(color_code[o:o + 2], 16) / 255.0 for o in offsets)
+
+HIGHLIGHT_COLOR = get_color('#00FFFF')
+BORDER_COLOR = get_color('#444444')
+
+# Missing blocks stuff
+MISSING_BLOCK_BACKGROUND_COLOR = get_color('#FFF2F2')
+MISSING_BLOCK_BORDER_COLOR = get_color('#FF0000')
+
+# Flow graph color constants
+FLOWGRAPH_BACKGROUND_COLOR = get_color('#FFFFFF')
+COMMENT_BACKGROUND_COLOR = get_color('#F3F3F3')
+FLOWGRAPH_EDGE_COLOR = COMMENT_BACKGROUND_COLOR
+
+# Block color constants
+BLOCK_ENABLED_COLOR = get_color('#F1ECFF')
+BLOCK_DISABLED_COLOR = get_color('#CCCCCC')
+BLOCK_BYPASSED_COLOR = get_color('#F4FF81')
+
+# Connection color constants
+CONNECTION_ENABLED_COLOR = get_color('#000000')
+CONNECTION_DISABLED_COLOR = get_color('#BBBBBB')
+CONNECTION_ERROR_COLOR = get_color('#FF0000')
+
+DEFAULT_DOMAIN_COLOR = get_color('#777777')
+
+#################################################################################
+# param box colors
+#################################################################################
+
+from gi.repository import Gdk
+
+
+def _color_parse(color_code):
+ color = Gdk.RGBA()
+ color.parse(color_code)
+ return color
+
+COMPLEX_COLOR_SPEC = _color_parse('#3399FF')
+FLOAT_COLOR_SPEC = _color_parse('#FF8C69')
+INT_COLOR_SPEC = _color_parse('#00FF99')
+SHORT_COLOR_SPEC = _color_parse('#FFFF66')
+BYTE_COLOR_SPEC = _color_parse('#FF66FF')
+
+ID_COLOR_SPEC = _color_parse('#DDDDDD')
+WILDCARD_COLOR_SPEC = _color_parse('#FFFFFF')
+
+COMPLEX_VECTOR_COLOR_SPEC = _color_parse('#3399AA')
+FLOAT_VECTOR_COLOR_SPEC = _color_parse('#CC8C69')
+INT_VECTOR_COLOR_SPEC = _color_parse('#00CC99')
+SHORT_VECTOR_COLOR_SPEC = _color_parse('#CCCC33')
+BYTE_VECTOR_COLOR_SPEC = _color_parse('#CC66CC')
+
+PARAM_ENTRY_COLORS = {
+
+ # Number types
+ 'complex': COMPLEX_COLOR_SPEC,
+ 'real': FLOAT_COLOR_SPEC,
+ 'float': FLOAT_COLOR_SPEC,
+ 'int': INT_COLOR_SPEC,
+
+ # Vector types
+ 'complex_vector': COMPLEX_VECTOR_COLOR_SPEC,
+ 'real_vector': FLOAT_VECTOR_COLOR_SPEC,
+ 'float_vector': FLOAT_VECTOR_COLOR_SPEC,
+ 'int_vector': INT_VECTOR_COLOR_SPEC,
+
+ # Special
+ 'bool': INT_COLOR_SPEC,
+ 'hex': INT_COLOR_SPEC,
+ 'string': BYTE_VECTOR_COLOR_SPEC,
+ 'id': ID_COLOR_SPEC,
+ 'stream_id': ID_COLOR_SPEC,
+ 'grid_pos': INT_VECTOR_COLOR_SPEC,
+ 'notebook': INT_VECTOR_COLOR_SPEC,
+ 'raw': WILDCARD_COLOR_SPEC,
+}
+
+PARAM_ENTRY_DEFAULT_COLOR = _color_parse('#FFFFFF')
+PARAM_ENTRY_ENUM_CUSTOM_COLOR = _color_parse('#EEEEEE')
diff --git a/grc/gui/Config.py b/grc/gui/Config.py
index 9b0c5d4afe..b6556ad724 100644
--- a/grc/gui/Config.py
+++ b/grc/gui/Config.py
@@ -17,8 +17,11 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+from __future__ import absolute_import, print_function
+
import sys
import os
+
from ..core.Config import Config as _Config
from . import Constants
@@ -57,7 +60,7 @@ class Config(_Config):
raise Exception()
return value
except:
- print >> sys.stderr, "Error: invalid 'canvas_default_size' setting."
+ print("Error: invalid 'canvas_default_size' setting.", file=sys.stderr)
return Constants.DEFAULT_CANVAS_SIZE_DEFAULT
@property
@@ -69,6 +72,6 @@ class Config(_Config):
raise Exception()
except:
font_size = Constants.DEFAULT_FONT_SIZE
- print >> sys.stderr, "Error: invalid 'canvas_font_size' setting."
+ print("Error: invalid 'canvas_font_size' setting.", file=sys.stderr)
return font_size
diff --git a/grc/gui/Connection.py b/grc/gui/Connection.py
index 50361c19d0..d893060aa6 100644
--- a/grc/gui/Connection.py
+++ b/grc/gui/Connection.py
@@ -17,14 +17,15 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import gtk
+from __future__ import absolute_import
-import Colors
-import Utils
-from Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT
-from Element import Element
+from six.moves import map
+
+from . import Colors
+from . import Utils
+from .Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT
+from .Element import Element
-from ..core.Constants import GR_MESSAGE_DOMAIN
from ..core.Connection import Connection as _Connection
@@ -73,27 +74,31 @@ class Connection(Element, _Connection):
self._source_coor = None
#get the source coordinate
try:
- connector_length = self.get_source().get_connector_length()
+ connector_length = self.source_port.get_connector_length()
except:
return
- self.x1, self.y1 = Utils.get_rotated_coordinate((connector_length, 0), self.get_source().get_rotation())
+ self.x1, self.y1 = Utils.get_rotated_coordinate((connector_length, 0), self.source_port.get_rotation())
#get the sink coordinate
- connector_length = self.get_sink().get_connector_length() + CONNECTOR_ARROW_HEIGHT
- self.x2, self.y2 = Utils.get_rotated_coordinate((-connector_length, 0), self.get_sink().get_rotation())
+ connector_length = self.sink_port.get_connector_length() + CONNECTOR_ARROW_HEIGHT
+ self.x2, self.y2 = Utils.get_rotated_coordinate((-connector_length, 0), self.sink_port.get_rotation())
#build the arrow
self.arrow = [(0, 0),
- Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE/2), self.get_sink().get_rotation()),
- Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, CONNECTOR_ARROW_BASE/2), self.get_sink().get_rotation()),
+ Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE/2), self.sink_port.get_rotation()),
+ Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, CONNECTOR_ARROW_BASE/2), self.sink_port.get_rotation()),
]
- source_domain = self.get_source().get_domain()
- sink_domain = self.get_sink().get_domain()
- self.line_attributes[0] = 2 if source_domain != sink_domain else 0
- self.line_attributes[1] = gtk.gdk.LINE_DOUBLE_DASH \
- if not source_domain == sink_domain == GR_MESSAGE_DOMAIN \
- else gtk.gdk.LINE_ON_OFF_DASH
- get_domain_color = lambda d: Colors.get_color((
- self.get_parent().get_parent().domains.get(d, {})
- ).get('color') or Colors.DEFAULT_DOMAIN_COLOR_CODE)
+ source_domain = self.source_port.get_domain()
+ sink_domain = self.sink_port.get_domain()
+ # self.line_attributes[0] = 2 if source_domain != sink_domain else 0
+ # self.line_attributes[1] = Gdk.LINE_DOUBLE_DASH \
+ # if not source_domain == sink_domain == GR_MESSAGE_DOMAIN \
+ # else Gdk.LINE_ON_OFF_DASH
+
+ def get_domain_color(domain_name):
+ domain = self.parent_platform.domains.get(domain_name, {})
+ color_spec = domain.get('color')
+ return Colors.get_color(color_spec) if color_spec else \
+ Colors.DEFAULT_DOMAIN_COLOR
+
self._color = get_domain_color(source_domain)
self._bg_color = get_domain_color(sink_domain)
self._arrow_color = self._bg_color if self.is_valid() else Colors.CONNECTION_ERROR_COLOR
@@ -103,12 +108,12 @@ class Connection(Element, _Connection):
"""Calculate coordinates."""
self.clear() #FIXME do i want this here?
#source connector
- source = self.get_source()
+ source = self.source_port
X, Y = source.get_connector_coordinate()
x1, y1 = self.x1 + X, self.y1 + Y
self.add_line((x1, y1), (X, Y))
#sink connector
- sink = self.get_sink()
+ sink = self.sink_port
X, Y = sink.get_connector_coordinate()
x2, y2 = self.x2 + X, self.y2 + Y
self.add_line((x2, y2), (X, Y))
@@ -126,7 +131,7 @@ class Connection(Element, _Connection):
#points[0][0] -> source connector should not be in the direction of source
if Utils.get_angle_from_coordinates(points[0][0], (x1, y1)) == source.get_connector_direction(): points.reverse()
#create 3-line connector
- p1, p2 = map(int, points[0][0]), map(int, points[0][1])
+ p1, p2 = list(map(int, points[0][0])), list(map(int, points[0][1]))
self.add_line((x1, y1), p1)
self.add_line(p1, p2)
self.add_line((x2, y2), p2)
@@ -143,16 +148,12 @@ class Connection(Element, _Connection):
self.add_line((x1, y1), points[0])
self.add_line((x2, y2), points[0])
- def draw(self, gc, window):
+ def draw(self, widget, cr):
"""
Draw the connection.
-
- Args:
- gc: the graphics context
- window: the gtk window to draw on
"""
- sink = self.get_sink()
- source = self.get_source()
+ sink = self.sink_port
+ source = self.source_port
#check for changes
if self._sink_rot != sink.get_rotation() or self._source_rot != source.get_rotation(): self.create_shapes()
elif self._sink_coor != sink.get_coordinate() or self._source_coor != source.get_coordinate():
@@ -171,11 +172,12 @@ class Connection(Element, _Connection):
Colors.CONNECTION_DISABLED_COLOR if not self.get_enabled() else
color
)
- Element.draw(self, gc, window, mod_color(self._color), mod_color(self._bg_color))
+ Element.draw(self, widget, cr, mod_color(self._color), mod_color(self._bg_color))
# draw arrow on sink port
- try:
- gc.set_foreground(mod_color(self._arrow_color))
- gc.set_line_attributes(0, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER)
- window.draw_polygon(gc, True, self._arrow)
- except:
- pass
+ cr.set_source_rgb(*self._arrow_color)
+ # TODO: gc.set_line_attributes(0, Gdk.LINE_SOLID, Gdk.CAP_BUTT, Gdk.JOIN_MITER)
+ cr.move_to(*self._arrow[0])
+ cr.line_to(*self._arrow[1])
+ cr.line_to(*self._arrow[2])
+ cr.close_path()
+ cr.fill()
diff --git a/grc/gui/Constants.py b/grc/gui/Constants.py
index 022564cd77..035a7f8ca9 100644
--- a/grc/gui/Constants.py
+++ b/grc/gui/Constants.py
@@ -17,7 +17,10 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import gtk
+from __future__ import absolute_import
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
from ..core.Constants import *
@@ -36,7 +39,7 @@ NEW_FLOGRAPH_TITLE = 'untitled'
MIN_WINDOW_WIDTH = 600
MIN_WINDOW_HEIGHT = 400
# dialog constraints
-MIN_DIALOG_WIDTH = 500
+MIN_DIALOG_WIDTH = 600
MIN_DIALOG_HEIGHT = 500
# default sizes
DEFAULT_BLOCKS_WINDOW_WIDTH = 100
@@ -53,7 +56,7 @@ PARAM_FONT = "Sans 7.5"
STATE_CACHE_SIZE = 42
# Shared targets for drag and drop of blocks
-DND_TARGETS = [('STRING', gtk.TARGET_SAME_APP, 0)]
+DND_TARGETS = [('STRING', Gtk.TargetFlags.SAME_APP, 0)]
# label constraint dimensions
LABEL_SEPARATION = 3
@@ -84,11 +87,8 @@ CONNECTOR_ARROW_HEIGHT = 17
# possible rotations in degrees
POSSIBLE_ROTATIONS = (0, 90, 180, 270)
-# How close can the mouse get to the window border before mouse events are ignored.
-BORDER_PROXIMITY_SENSITIVITY = 50
-
# How close the mouse can get to the edge of the visible window before scrolling is invoked.
-SCROLL_PROXIMITY_SENSITIVITY = 30
+SCROLL_PROXIMITY_SENSITIVITY = 50
# When the window has to be scrolled, move it this distance in the required direction.
SCROLL_DISTANCE = 15
@@ -96,6 +96,13 @@ SCROLL_DISTANCE = 15
# How close the mouse click can be to a line and register a connection select.
LINE_SELECT_SENSITIVITY = 5
+DEFAULT_BLOCK_MODULE_TOOLTIP = """\
+This subtree holds all blocks (from OOT modules) that specify no module name. \
+The module name is the root category enclosed in square brackets.
+
+Please consider contacting OOT module maintainer for any block in here \
+and kindly ask to update their GRC Block Descriptions or Block Tree to include a module name."""
+
def update_font_size(font_size):
global PORT_SEPARATION, BLOCK_FONT, PORT_FONT, PARAM_FONT, FONT_SIZE
diff --git a/grc/gui/Dialogs.py b/grc/gui/Dialogs.py
index 1d114356c8..8f0f60d764 100644
--- a/grc/gui/Dialogs.py
+++ b/grc/gui/Dialogs.py
@@ -17,7 +17,12 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import gtk
+from __future__ import absolute_import
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import GObject
import sys
from distutils.spawn import find_executable
@@ -26,7 +31,7 @@ from . import Utils, Actions
from ..core import Messages
-class SimpleTextDisplay(gtk.TextView):
+class SimpleTextDisplay(Gtk.TextView):
"""A non editable gtk text view."""
def __init__(self, text=''):
@@ -36,13 +41,13 @@ class SimpleTextDisplay(gtk.TextView):
Args:
text: the text to display (string)
"""
- text_buffer = gtk.TextBuffer()
+ text_buffer = Gtk.TextBuffer()
text_buffer.set_text(text)
self.set_text = text_buffer.set_text
- gtk.TextView.__init__(self, text_buffer)
+ GObject.GObject.__init__(self)
self.set_editable(False)
self.set_cursor_visible(False)
- self.set_wrap_mode(gtk.WRAP_WORD_CHAR)
+ self.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
class TextDisplay(SimpleTextDisplay):
@@ -85,7 +90,8 @@ class TextDisplay(SimpleTextDisplay):
if self.scroll_lock:
buffer = self.get_buffer()
buffer.move_mark(buffer.get_insert(), buffer.get_end_iter())
- self.scroll_to_mark(buffer.get_insert(), 0.0)
+ # TODO: Fix later
+ #self.scroll_to_mark(buffer.get_insert(), 0.0)
def clear(self):
buffer = self.get_buffer()
@@ -110,18 +116,18 @@ class TextDisplay(SimpleTextDisplay):
def populate_popup(self, view, menu):
"""Create a popup menu for the scroll lock and clear functions"""
- menu.append(gtk.SeparatorMenuItem())
+ menu.append(Gtk.SeparatorMenuItem())
- lock = gtk.CheckMenuItem("Scroll Lock")
+ lock = Gtk.CheckMenuItem("Scroll Lock")
menu.append(lock)
lock.set_active(self.scroll_lock)
lock.connect('activate', self.scroll_back_cb, view)
- save = gtk.ImageMenuItem(gtk.STOCK_SAVE)
+ save = Gtk.ImageMenuItem(Gtk.STOCK_SAVE)
menu.append(save)
save.connect('activate', self.save_cb, view)
- clear = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
+ clear = Gtk.ImageMenuItem(Gtk.STOCK_CLEAR)
menu.append(clear)
clear.connect('activate', self.clear_cb, view)
menu.show_all()
@@ -133,9 +139,9 @@ def MessageDialogHelper(type, buttons, title=None, markup=None, default_response
Create a modal message dialog and run it.
Args:
- type: the type of message: gtk.MESSAGE_INFO, gtk.MESSAGE_WARNING, gtk.MESSAGE_QUESTION or gtk.MESSAGE_ERROR
+ type: the type of message: Gtk.MessageType.INFO, Gtk.MessageType.WARNING, Gtk.MessageType.QUESTION or Gtk.MessageType.ERROR
buttons: the predefined set of buttons to use:
- gtk.BUTTONS_NONE, gtk.BUTTONS_OK, gtk.BUTTONS_CLOSE, gtk.BUTTONS_CANCEL, gtk.BUTTONS_YES_NO, gtk.BUTTONS_OK_CANCEL
+ Gtk.ButtonsType.NONE, Gtk.ButtonsType.OK, Gtk.ButtonsType.CLOSE, Gtk.ButtonsType.CANCEL, Gtk.ButtonsType.YES_NO, Gtk.ButtonsType.OK_CANCEL
Args:
title: the title of the window (string)
@@ -146,7 +152,7 @@ def MessageDialogHelper(type, buttons, title=None, markup=None, default_response
Returns:
the gtk response from run()
"""
- message_dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, type, buttons)
+ message_dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, type, buttons)
if title: message_dialog.set_title(title)
if markup: message_dialog.set_markup(markup)
if extra_buttons: message_dialog.add_buttons(*extra_buttons)
@@ -156,28 +162,24 @@ def MessageDialogHelper(type, buttons, title=None, markup=None, default_response
return response
-ERRORS_MARKUP_TMPL="""\
-#for $i, $err_msg in enumerate($errors)
-<b>Error $i:</b>
-$encode($err_msg.replace('\t', ' '))
-
-#end for"""
-
-
-def ErrorsDialog(flowgraph): MessageDialogHelper(
- type=gtk.MESSAGE_ERROR,
- buttons=gtk.BUTTONS_CLOSE,
- title='Flow Graph Errors',
- markup=Utils.parse_template(ERRORS_MARKUP_TMPL, errors=flowgraph.get_error_messages()),
-)
+def ErrorsDialog(flowgraph):
+ MessageDialogHelper(
+ type=Gtk.MessageType.ERROR,
+ buttons=Gtk.ButtonsType.CLOSE,
+ title='Flow Graph Errors',
+ markup='\n\n'.join(
+ '<b>Error {num}:</b>\n{msg}'.format(num=i, msg=Utils.encode(msg.replace('\t', ' ')))
+ for i, msg in enumerate(flowgraph.get_error_messages())
+ ),
+ )
-class AboutDialog(gtk.AboutDialog):
+class AboutDialog(Gtk.AboutDialog):
"""A cute little about dialog."""
def __init__(self, config):
"""AboutDialog constructor."""
- gtk.AboutDialog.__init__(self)
+ GObject.GObject.__init__(self)
self.set_name(config.name)
self.set_version(config.version)
self.set_license(config.license)
@@ -188,8 +190,8 @@ class AboutDialog(gtk.AboutDialog):
def HelpDialog(): MessageDialogHelper(
- type=gtk.MESSAGE_INFO,
- buttons=gtk.BUTTONS_CLOSE,
+ type=Gtk.MessageType.INFO,
+ buttons=Gtk.ButtonsType.CLOSE,
title='Help',
markup="""\
<b>Usage Tips</b>
@@ -203,32 +205,28 @@ def HelpDialog(): MessageDialogHelper(
* See the menu for other keyboard shortcuts.""")
-COLORS_DIALOG_MARKUP_TMPL = """\
-<b>Color Mapping</b>
-
-#if $colors
- #set $max_len = max([len(color[0]) for color in $colors]) + 10
- #for $title, $color_spec in $colors
-<span background="$color_spec"><tt>$($encode($title).center($max_len))</tt></span>
- #end for
-#end if
-"""
-
def TypesDialog(platform):
+ colors = platform.get_colors()
+ max_len = 10 + max(len(name) for name, code in colors)
+
+ message = '\n'.join(
+ '<span background="{color}"><tt>{name}</tt></span>'
+ ''.format(color=color, name=Utils.encode(name).center(max_len))
+ for name, color in colors
+ )
MessageDialogHelper(
- type=gtk.MESSAGE_INFO,
- buttons=gtk.BUTTONS_CLOSE,
- title='Types',
- markup=Utils.parse_template(COLORS_DIALOG_MARKUP_TMPL,
- colors=platform.get_colors())
+ type=Gtk.MessageType.INFO,
+ buttons=Gtk.ButtonsType.CLOSE,
+ title='Types - Color Mapping',
+ markup=message
)
def MissingXTermDialog(xterm):
MessageDialogHelper(
- type=gtk.MESSAGE_WARNING,
- buttons=gtk.BUTTONS_OK,
+ type=Gtk.MessageType.WARNING,
+ buttons=Gtk.ButtonsType.OK,
title='Warning: missing xterm executable',
markup=("The xterm executable {0!r} is missing.\n\n"
"You can change this setting in your gnuradio.conf, in "
@@ -242,28 +240,28 @@ def ChooseEditorDialog(config):
# Give the option to either choose an editor or use the default
# Always return true/false so the caller knows it was successful
buttons = (
- 'Choose Editor', gtk.RESPONSE_YES,
- 'Use Default', gtk.RESPONSE_NO,
- gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL
+ 'Choose Editor', Gtk.ResponseType.YES,
+ 'Use Default', Gtk.ResponseType.NO,
+ Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL
)
response = MessageDialogHelper(
- gtk.MESSAGE_QUESTION, gtk.BUTTONS_NONE, 'Choose Editor',
- 'Would you like to choose the editor to use?', gtk.RESPONSE_YES, buttons
+ Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, 'Choose Editor',
+ 'Would you like to choose the editor to use?', Gtk.ResponseType.YES, buttons
)
# Handle the inital default/choose/cancel response
# User wants to choose the editor to use
- if response == gtk.RESPONSE_YES:
- file_dialog = gtk.FileChooserDialog(
+ if response == Gtk.ResponseType.YES:
+ file_dialog = Gtk.FileChooserDialog(
'Select an Editor...', None,
- gtk.FILE_CHOOSER_ACTION_OPEN,
- ('gtk-cancel', gtk.RESPONSE_CANCEL, 'gtk-open', gtk.RESPONSE_OK)
+ Gtk.FileChooserAction.OPEN,
+ ('gtk-cancel', Gtk.ResponseType.CANCEL, 'gtk-open', Gtk.ResponseType.OK)
)
file_dialog.set_select_multiple(False)
file_dialog.set_local_only(True)
file_dialog.set_current_folder('/usr/bin')
try:
- if file_dialog.run() == gtk.RESPONSE_OK:
+ if file_dialog.run() == Gtk.ResponseType.OK:
config.editor = file_path = file_dialog.get_filename()
file_dialog.destroy()
return file_path
@@ -271,7 +269,7 @@ def ChooseEditorDialog(config):
file_dialog.destroy()
# Go with the default editor
- elif response == gtk.RESPONSE_NO:
+ elif response == Gtk.ResponseType.NO:
# Determine the platform
try:
process = None
diff --git a/grc/gui/DrawingArea.py b/grc/gui/DrawingArea.py
index 6a1df27a8c..33c669c99f 100644
--- a/grc/gui/DrawingArea.py
+++ b/grc/gui/DrawingArea.py
@@ -17,15 +17,14 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import pygtk
-pygtk.require('2.0')
-import gtk
+from __future__ import absolute_import
+from gi.repository import Gtk, Gdk, GObject
-from Constants import MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT, DND_TARGETS
-import Colors
+from . import Constants, Colors
-class DrawingArea(gtk.DrawingArea):
+
+class DrawingArea(Gtk.DrawingArea):
"""
DrawingArea is the gtk pixel map that graphical elements may draw themselves on.
The drawing area also responds to mouse and key events.
@@ -39,52 +38,47 @@ class DrawingArea(gtk.DrawingArea):
Args:
main_window: the main_window containing all flow graphs
"""
+ Gtk.DrawingArea.__init__(self)
+
+ self._flow_graph = flow_graph
+
+ self.zoom_factor = 1.0
self.ctrl_mask = False
self.mod1_mask = False
- self._flow_graph = flow_graph
- gtk.DrawingArea.__init__(self)
- self.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
+ self.button_state = [False] * 10
+
+ # self.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
self.connect('realize', self._handle_window_realize)
- self.connect('configure-event', self._handle_window_configure)
- self.connect('expose-event', self._handle_window_expose)
+ self.connect('draw', self.draw)
self.connect('motion-notify-event', self._handle_mouse_motion)
self.connect('button-press-event', self._handle_mouse_button_press)
self.connect('button-release-event', self._handle_mouse_button_release)
self.connect('scroll-event', self._handle_mouse_scroll)
self.add_events(
- gtk.gdk.BUTTON_PRESS_MASK | \
- gtk.gdk.POINTER_MOTION_MASK | \
- gtk.gdk.BUTTON_RELEASE_MASK | \
- gtk.gdk.LEAVE_NOTIFY_MASK | \
- gtk.gdk.ENTER_NOTIFY_MASK | \
- gtk.gdk.FOCUS_CHANGE_MASK
+ Gdk.EventMask.BUTTON_PRESS_MASK |
+ Gdk.EventMask.POINTER_MOTION_MASK |
+ Gdk.EventMask.BUTTON_RELEASE_MASK |
+ Gdk.EventMask.SCROLL_MASK |
+ Gdk.EventMask.LEAVE_NOTIFY_MASK |
+ Gdk.EventMask.ENTER_NOTIFY_MASK
+ #Gdk.EventMask.FOCUS_CHANGE_MASK
)
- #setup drag and drop
- self.drag_dest_set(gtk.DEST_DEFAULT_ALL, DND_TARGETS, gtk.gdk.ACTION_COPY)
+
+ # setup drag and drop
+ self.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY)
self.connect('drag-data-received', self._handle_drag_data_received)
- #setup the focus flag
+ self.drag_dest_set_target_list(None)
+ self.drag_dest_add_text_targets()
+
+ # setup the focus flag
self._focus_flag = False
self.get_focus_flag = lambda: self._focus_flag
def _handle_notify_event(widget, event, focus_flag): self._focus_flag = focus_flag
self.connect('leave-notify-event', _handle_notify_event, False)
self.connect('enter-notify-event', _handle_notify_event, True)
- self.set_flags(gtk.CAN_FOCUS) # self.set_can_focus(True)
- self.connect('focus-out-event', self._handle_focus_lost_event)
-
- def new_pixmap(self, width, height):
- return gtk.gdk.Pixmap(self.window, width, height, -1)
-
- def get_screenshot(self, transparent_bg=False):
- pixmap = self._pixmap
- W, H = pixmap.get_size()
- pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, W, H)
- pixbuf.fill(0xFF + Colors.FLOWGRAPH_BACKGROUND_COLOR.pixel << 8)
- pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, W-1, H-1)
- if transparent_bg:
- bgc = Colors.FLOWGRAPH_BACKGROUND_COLOR
- pixbuf = pixbuf.add_alpha(True, bgc.red, bgc.green, bgc.blue)
- return pixbuf
-
+ # todo: fix
+# self.set_flags(Gtk.CAN_FOCUS) # self.set_can_focus(True)
+# self.connect('focus-out-event', self._handle_focus_lost_event)
##########################################################################
## Handlers
@@ -93,83 +87,120 @@ class DrawingArea(gtk.DrawingArea):
"""
Handle a drag and drop by adding a block at the given coordinate.
"""
- self._flow_graph.add_new_block(selection_data.data, (x, y))
+ self._flow_graph.add_new_block(selection_data.get_text(), (x, y))
def _handle_mouse_scroll(self, widget, event):
- if event.state & gtk.gdk.SHIFT_MASK:
- if event.direction == gtk.gdk.SCROLL_UP:
- event.direction = gtk.gdk.SCROLL_LEFT
- else:
- event.direction = gtk.gdk.SCROLL_RIGHT
+ if event.get_state() & Gdk.ModifierType.SHIFT_MASK:
+ if event.direction == Gdk.ScrollDirection.UP:
+ event.direction = Gdk.ScrollDirection.LEFT
+ else:
+ event.direction = Gdk.ScrollDirection.RIGHT
+
+ elif event.get_state() & Gdk.ModifierType.CONTROL_MASK:
+ change = 1.2 if event.direction == Gdk.ScrollDirection.UP else 1/1.2
+ zoom_factor = min(max(self.zoom_factor * change, 0.1), 5.0)
+
+ if zoom_factor != self.zoom_factor:
+ self.zoom_factor = zoom_factor
+ self.queue_draw()
+ return True
+
+ return False
def _handle_mouse_button_press(self, widget, event):
"""
Forward button click information to the flow graph.
"""
self.grab_focus()
- self.ctrl_mask = event.state & gtk.gdk.CONTROL_MASK
- self.mod1_mask = event.state & gtk.gdk.MOD1_MASK
- if event.button == 1: self._flow_graph.handle_mouse_selector_press(
- double_click=(event.type == gtk.gdk._2BUTTON_PRESS),
- coordinate=(event.x, event.y),
- )
- if event.button == 3: self._flow_graph.handle_mouse_context_press(
- coordinate=(event.x, event.y),
- event=event,
- )
+ self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK
+ self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK
+ self.button_state[event.button] = True
+
+ if event.button == 1:
+ self._flow_graph.handle_mouse_selector_press(
+ double_click=(event.type == Gdk.EventType._2BUTTON_PRESS),
+ coordinate=self._translate_event_coords(event),
+ )
+ elif event.button == 3:
+ self._flow_graph.handle_mouse_context_press(
+ coordinate=self._translate_event_coords(event),
+ event=event,
+ )
def _handle_mouse_button_release(self, widget, event):
"""
Forward button release information to the flow graph.
"""
- self.ctrl_mask = event.state & gtk.gdk.CONTROL_MASK
- self.mod1_mask = event.state & gtk.gdk.MOD1_MASK
- if event.button == 1: self._flow_graph.handle_mouse_selector_release(
- coordinate=(event.x, event.y),
- )
+ self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK
+ self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK
+ self.button_state[event.button] = False
+ if event.button == 1:
+ self._flow_graph.handle_mouse_selector_release(
+ coordinate=self._translate_event_coords(event),
+ )
def _handle_mouse_motion(self, widget, event):
"""
Forward mouse motion information to the flow graph.
"""
- self.ctrl_mask = event.state & gtk.gdk.CONTROL_MASK
- self.mod1_mask = event.state & gtk.gdk.MOD1_MASK
+ self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK
+ self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK
+
+ if self.button_state[1]:
+ self._auto_scroll(event)
+
self._flow_graph.handle_mouse_motion(
- coordinate=(event.x, event.y),
+ coordinate=self._translate_event_coords(event),
+ button1_pressed=self.button_state[1]
)
+ def _auto_scroll(self, event):
+ x, y = event.x, event.y
+ scrollbox = self.get_parent().get_parent()
+
+ w, h = self._flow_graph.get_max_coords(initial=(x, y))
+ self.set_size_request(w + 100, h + 100)
+
+ def scroll(pos, adj):
+ """scroll if we moved near the border"""
+ adj_val = adj.get_value()
+ adj_len = adj.get_page_size()
+ if pos - adj_val > adj_len - Constants.SCROLL_PROXIMITY_SENSITIVITY:
+ adj.set_value(adj_val + Constants.SCROLL_DISTANCE)
+ adj.emit('changed')
+ elif pos - adj_val < Constants.SCROLL_PROXIMITY_SENSITIVITY:
+ adj.set_value(adj_val - Constants.SCROLL_DISTANCE)
+ adj.emit('changed')
+
+ scroll(x, scrollbox.get_hadjustment())
+ scroll(y, scrollbox.get_vadjustment())
+
def _handle_window_realize(self, widget):
"""
Called when the window is realized.
Update the flowgraph, which calls new pixmap.
"""
self._flow_graph.update()
+ w, h = self._flow_graph.get_max_coords()
+ self.set_size_request(w + 100, h + 100)
- def _handle_window_configure(self, widget, event):
- """
- Called when the window is resized.
- Create a new pixmap for background buffer.
- """
- self._pixmap = self.new_pixmap(*self.get_size_request())
+ def draw(self, widget, cr):
+ width = widget.get_allocated_width()
+ height = widget.get_allocated_height()
+ cr.set_source_rgb(*Colors.FLOWGRAPH_BACKGROUND_COLOR)
+ cr.rectangle(0, 0, width, height)
- def _handle_window_expose(self, widget, event):
- """
- Called when window is exposed, or queue_draw is called.
- Double buffering: draw to pixmap, then draw pixmap to window.
- """
- gc = self.window.new_gc()
- self._flow_graph.draw(gc, self._pixmap)
- self.window.draw_drawable(gc, self._pixmap, 0, 0, 0, 0, -1, -1)
- # draw a light grey line on the bottom and right end of the canvas.
- # this is useful when the theme uses the same panel bg color as the canvas
- W, H = self._pixmap.get_size()
- gc.set_foreground(Colors.FLOWGRAPH_EDGE_COLOR)
- self.window.draw_line(gc, 0, H-1, W, H-1)
- self.window.draw_line(gc, W-1, 0, W-1, H)
+ cr.scale(self.zoom_factor, self.zoom_factor)
+ cr.fill()
+
+ self._flow_graph.draw(widget, cr)
+
+ def _translate_event_coords(self, event):
+ return event.x / self.zoom_factor, event.y / self.zoom_factor
def _handle_focus_lost_event(self, widget, event):
# don't clear selection while context menu is active
- if not self._flow_graph.get_context_menu().flags() & gtk.VISIBLE:
+ if not self._flow_graph.get_context_menu().flags() & Gtk.VISIBLE:
self._flow_graph.unselect()
self._flow_graph.update_selected()
self._flow_graph.queue_draw()
diff --git a/grc/gui/Element.py b/grc/gui/Element.py
index 9385424772..4e88df375f 100644
--- a/grc/gui/Element.py
+++ b/grc/gui/Element.py
@@ -17,10 +17,15 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-from Constants import LINE_SELECT_SENSITIVITY
-from Constants import POSSIBLE_ROTATIONS
+from __future__ import absolute_import
+from .Constants import LINE_SELECT_SENSITIVITY
+from .Constants import POSSIBLE_ROTATIONS
-import gtk
+import gi
+from six.moves import zip
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+from gi.repository import Gdk
class Element(object):
@@ -38,9 +43,11 @@ class Element(object):
self.set_coordinate((0, 0))
self.clear()
self.set_highlighted(False)
- self.line_attributes = [
- 0, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER
- ]
+ self.line_attributes = []
+ """ # No idea where this is in pygobject
+ 0, Gdk.LINE_SOLID, Gdk.CAP_BUTT, Gdk.JOIN_MITER
+ ]"""
+
def is_horizontal(self, rotation=None):
"""
@@ -85,29 +92,33 @@ class Element(object):
self.clear()
for child in self.get_children(): child.create_shapes()
- def draw(self, gc, window, border_color, bg_color):
+ def draw(self, widget, cr, border_color, bg_color):
"""
Draw in the given window.
Args:
- gc: the graphics context
- window: the gtk window to draw on
+ widget:
+ cr:
border_color: the color for lines and rectangle borders
bg_color: the color for the inside of the rectangle
"""
X, Y = self.get_coordinate()
- gc.set_line_attributes(*self.line_attributes)
+ # TODO: gc.set_line_attributes(*self.line_attributes)
for (rX, rY), (W, H) in self._areas_list:
aX = X + rX
aY = Y + rY
- gc.set_foreground(bg_color)
- window.draw_rectangle(gc, True, aX, aY, W, H)
- gc.set_foreground(border_color)
- window.draw_rectangle(gc, False, aX, aY, W, H)
+ cr.set_source_rgb(*bg_color)
+ cr.rectangle(aX, aY, W, H)
+ cr.fill()
+ cr.set_source_rgb(*border_color)
+ cr.rectangle(aX, aY, W, H)
+ cr.stroke()
+
for (x1, y1), (x2, y2) in self._lines_list:
- gc.set_foreground(border_color)
- gc.set_background(bg_color)
- window.draw_line(gc, X+x1, Y+y1, X+x2, Y+y2)
+ cr.set_source_rgb(*border_color)
+ cr.move_to(X + x1, Y + y1)
+ cr.line_to(X + x2, Y + y2)
+ cr.stroke()
def rotate(self, rotation):
"""
diff --git a/grc/gui/Executor.py b/grc/gui/Executor.py
index bf9eecb9a8..a8c67986e5 100644
--- a/grc/gui/Executor.py
+++ b/grc/gui/Executor.py
@@ -15,7 +15,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-import gobject
+from __future__ import absolute_import
import os
import threading
import shlex
@@ -24,6 +24,8 @@ import sys
import re
from distutils.spawn import find_executable
+from gi.repository import GLib
+
from ..core import Messages
@@ -44,8 +46,7 @@ class ExecFlowGraphThread(threading.Thread):
self.update_callback = callback
try:
- self.process = self._popen()
- self.page.set_proc(self.process)
+ self.process = self.page.process = self._popen()
self.update_callback()
self.start()
except Exception as e:
@@ -56,7 +57,7 @@ class ExecFlowGraphThread(threading.Thread):
"""
Execute this python flow graph.
"""
- run_command = self.page.get_flow_graph().get_option('run_command')
+ run_command = self.page.flow_graph.get_option('run_command')
generator = self.page.get_generator()
try:
@@ -86,20 +87,20 @@ class ExecFlowGraphThread(threading.Thread):
def run(self):
"""
Wait on the executing process by reading from its stdout.
- Use gobject.idle_add when calling functions that modify gtk objects.
+ Use GObject.idle_add when calling functions that modify gtk objects.
"""
# handle completion
r = "\n"
while r:
- gobject.idle_add(Messages.send_verbose_exec, r)
+ GLib.idle_add(Messages.send_verbose_exec, r)
r = os.read(self.process.stdout.fileno(), 1024)
self.process.poll()
- gobject.idle_add(self.done)
+ GLib.idle_add(self.done)
def done(self):
"""Perform end of execution tasks."""
Messages.send_end_exec(self.process.returncode)
- self.page.set_proc(None)
+ self.page.process = None
self.update_callback()
diff --git a/grc/gui/FileDialogs.py b/grc/gui/FileDialogs.py
index e9430b1f88..3ee715dac6 100644
--- a/grc/gui/FileDialogs.py
+++ b/grc/gui/FileDialogs.py
@@ -17,16 +17,19 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import pygtk
-pygtk.require('2.0')
-import gtk
-from Dialogs import MessageDialogHelper
-from Constants import \
+from __future__ import absolute_import
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+from gi.repository import GObject
+
+from .Dialogs import MessageDialogHelper
+from .Constants import \
DEFAULT_FILE_PATH, IMAGE_FILE_EXTENSION, TEXT_FILE_EXTENSION, \
NEW_FLOGRAPH_TITLE
-import Preferences
+from . import Preferences
from os import path
-import Utils
+from . import Utils
##################################################
# Constants
@@ -37,52 +40,45 @@ SAVE_CONSOLE = 'save console'
SAVE_IMAGE = 'save image'
OPEN_QSS_THEME = 'open qss theme'
-FILE_OVERWRITE_MARKUP_TMPL="""\
-File <b>$encode($filename)</b> Exists!\nWould you like to overwrite the existing file?"""
-
-FILE_DNE_MARKUP_TMPL="""\
-File <b>$encode($filename)</b> Does not Exist!"""
-
-
# File Filters
def get_flow_graph_files_filter():
- filter = gtk.FileFilter()
+ filter = Gtk.FileFilter()
filter.set_name('Flow Graph Files')
filter.add_pattern('*'+Preferences.file_extension())
return filter
def get_text_files_filter():
- filter = gtk.FileFilter()
+ filter = Gtk.FileFilter()
filter.set_name('Text Files')
filter.add_pattern('*'+TEXT_FILE_EXTENSION)
return filter
def get_image_files_filter():
- filter = gtk.FileFilter()
+ filter = Gtk.FileFilter()
filter.set_name('Image Files')
filter.add_pattern('*'+IMAGE_FILE_EXTENSION)
return filter
def get_all_files_filter():
- filter = gtk.FileFilter()
+ filter = Gtk.FileFilter()
filter.set_name('All Files')
filter.add_pattern('*')
return filter
def get_qss_themes_filter():
- filter = gtk.FileFilter()
+ filter = Gtk.FileFilter()
filter.set_name('QSS Themes')
filter.add_pattern('*.qss')
return filter
# File Dialogs
-class FileDialogHelper(gtk.FileChooserDialog):
+class FileDialogHelper(Gtk.FileChooserDialog):
"""
A wrapper class for the gtk file chooser dialog.
Implement a file chooser dialog with only necessary parameters.
@@ -95,11 +91,11 @@ class FileDialogHelper(gtk.FileChooserDialog):
Use standard settings: no multiple selection, local files only, and the * filter.
Args:
- action: gtk.FILE_CHOOSER_ACTION_OPEN or gtk.FILE_CHOOSER_ACTION_SAVE
+ action: Gtk.FileChooserAction.OPEN or Gtk.FileChooserAction.SAVE
title: the title of the dialog (string)
"""
- ok_stock = {gtk.FILE_CHOOSER_ACTION_OPEN : 'gtk-open', gtk.FILE_CHOOSER_ACTION_SAVE : 'gtk-save'}[action]
- gtk.FileChooserDialog.__init__(self, title, None, action, ('gtk-cancel', gtk.RESPONSE_CANCEL, ok_stock, gtk.RESPONSE_OK))
+ ok_stock = {Gtk.FileChooserAction.OPEN : 'gtk-open', Gtk.FileChooserAction.SAVE : 'gtk-save'}[action]
+ GObject.GObject.__init__(self, title, None, action, ('gtk-cancel', Gtk.ResponseType.CANCEL, ok_stock, Gtk.ResponseType.OK))
self.set_select_multiple(False)
self.set_local_only(True)
self.add_filter(get_all_files_filter())
@@ -117,25 +113,25 @@ class FileDialog(FileDialogHelper):
"""
if not current_file_path: current_file_path = path.join(DEFAULT_FILE_PATH, NEW_FLOGRAPH_TITLE + Preferences.file_extension())
if self.type == OPEN_FLOW_GRAPH:
- FileDialogHelper.__init__(self, gtk.FILE_CHOOSER_ACTION_OPEN, 'Open a Flow Graph from a File...')
+ FileDialogHelper.__init__(self, Gtk.FileChooserAction.OPEN, 'Open a Flow Graph from a File...')
self.add_and_set_filter(get_flow_graph_files_filter())
self.set_select_multiple(True)
elif self.type == SAVE_FLOW_GRAPH:
- FileDialogHelper.__init__(self, gtk.FILE_CHOOSER_ACTION_SAVE, 'Save a Flow Graph to a File...')
+ FileDialogHelper.__init__(self, Gtk.FileChooserAction.SAVE, 'Save a Flow Graph to a File...')
self.add_and_set_filter(get_flow_graph_files_filter())
self.set_current_name(path.basename(current_file_path))
elif self.type == SAVE_CONSOLE:
- FileDialogHelper.__init__(self, gtk.FILE_CHOOSER_ACTION_SAVE, 'Save Console to a File...')
+ FileDialogHelper.__init__(self, Gtk.FileChooserAction.SAVE, 'Save Console to a File...')
self.add_and_set_filter(get_text_files_filter())
file_path = path.splitext(path.basename(current_file_path))[0]
self.set_current_name(file_path) #show the current filename
elif self.type == SAVE_IMAGE:
- FileDialogHelper.__init__(self, gtk.FILE_CHOOSER_ACTION_SAVE, 'Save a Flow Graph Screen Shot...')
+ FileDialogHelper.__init__(self, Gtk.FileChooserAction.SAVE, 'Save a Flow Graph Screen Shot...')
self.add_and_set_filter(get_image_files_filter())
current_file_path = current_file_path + IMAGE_FILE_EXTENSION
self.set_current_name(path.basename(current_file_path)) #show the current filename
elif self.type == OPEN_QSS_THEME:
- FileDialogHelper.__init__(self, gtk.FILE_CHOOSER_ACTION_OPEN, 'Open a QSS theme...')
+ FileDialogHelper.__init__(self, Gtk.FileChooserAction.OPEN, 'Open a QSS theme...')
self.add_and_set_filter(get_qss_themes_filter())
self.set_select_multiple(False)
self.set_current_folder(path.dirname(current_file_path)) #current directory
@@ -160,7 +156,7 @@ class FileDialog(FileDialogHelper):
Returns:
the complete file path
"""
- if gtk.FileChooserDialog.run(self) != gtk.RESPONSE_OK: return None #response was cancel
+ if Gtk.FileChooserDialog.run(self) != Gtk.ResponseType.OK: return None #response was cancel
#############################################
# Handle Save Dialogs
#############################################
@@ -176,9 +172,10 @@ class FileDialog(FileDialogHelper):
self.set_current_name(path.basename(filename)) #show the filename with extension
if path.exists(filename): #ask the user to confirm overwrite
if MessageDialogHelper(
- gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, 'Confirm Overwrite!',
- Utils.parse_template(FILE_OVERWRITE_MARKUP_TMPL, filename=filename),
- ) == gtk.RESPONSE_NO: return self.get_rectified_filename()
+ Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, 'Confirm Overwrite!',
+ 'File <b>{filename}</b> Exists!\nWould you like to overwrite the existing file?'
+ ''.format(filename=Utils.encode(filename)),
+ ) == Gtk.ResponseType.NO: return self.get_rectified_filename()
return filename
#############################################
# Handle Open Dialogs
@@ -188,8 +185,8 @@ class FileDialog(FileDialogHelper):
for filename in filenames:
if not path.exists(filename): #show a warning and re-run
MessageDialogHelper(
- gtk.MESSAGE_WARNING, gtk.BUTTONS_CLOSE, 'Cannot Open!',
- Utils.parse_template(FILE_DNE_MARKUP_TMPL, filename=filename),
+ Gtk.MessageType.WARNING, Gtk.ButtonsType.CLOSE, 'Cannot Open!',
+ 'File <b>{filename}</b> Does not Exist!'.format(filename=Utils.encode(filename)),
)
return self.get_rectified_filename()
return filenames
@@ -230,7 +227,7 @@ class SaveScreenShotDialog(SaveImageFileDialog):
def __init__(self, current_file_path=''):
SaveImageFileDialog.__init__(self, current_file_path)
- self._button = button = gtk.CheckButton('_Background transparent')
+ self._button = button = Gtk.CheckButton('_Background transparent')
self._button.set_active(Preferences.screen_shot_background_transparent())
self.set_extra_widget(button)
diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py
index f98aec41d5..d7745a529d 100644
--- a/grc/gui/FlowGraph.py
+++ b/grc/gui/FlowGraph.py
@@ -17,16 +17,20 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+from __future__ import absolute_import
+
import functools
import random
from distutils.spawn import find_executable
from itertools import chain, count
from operator import methodcaller
-import gobject
+import six
+from six.moves import filter
+
+from gi.repository import GObject
-from . import Actions, Colors, Constants, Utils, Bars, Dialogs
-from .Constants import SCROLL_PROXIMITY_SENSITIVITY, SCROLL_DISTANCE
+from . import Actions, Colors, Utils, Bars, Dialogs
from .Element import Element
from .external_editor import ExternalEditor
@@ -76,19 +80,20 @@ class FlowGraph(Element, _Flowgraph):
Returns:
a unique id
"""
+ block_ids = set(b.get_id() for b in self.blocks)
for index in count():
block_id = '{}_{}'.format(base_id, index)
- if block_id not in (b.get_id() for b in self.blocks):
+ if block_id not in block_ids:
break
return block_id
def install_external_editor(self, param):
- target = (param.get_parent().get_id(), param.get_key())
+ target = (param.parent_block.get_id(), param.get_key())
if target in self._external_updaters:
editor = self._external_updaters[target]
else:
- config = self.get_parent().config
+ config = self.parent_platform.config
editor = (find_executable(config.editor) or
Dialogs.ChooseEditorDialog(config))
if not editor:
@@ -98,7 +103,7 @@ class FlowGraph(Element, _Flowgraph):
editor = self._external_updaters[target] = ExternalEditor(
editor=editor,
name=target[0], value=param.get_value(),
- callback=functools.partial(gobject.idle_add, updater)
+ callback=functools.partial(GObject.idle_add, updater)
)
editor.start()
try:
@@ -107,7 +112,7 @@ class FlowGraph(Element, _Flowgraph):
# Problem launching the editor. Need to select a new editor.
Messages.send('>>> Error opening an external editor. Please select a different editor.\n')
# Reset the editor to force the user to select a new one.
- self.get_parent().config.editor = ''
+ self.parent_platform.config.editor = ''
def handle_external_editor_change(self, new_value, target):
try:
@@ -126,9 +131,7 @@ class FlowGraph(Element, _Flowgraph):
###########################################################################
def get_drawing_area(self): return self.drawing_area
def queue_draw(self): self.get_drawing_area().queue_draw()
- def get_size(self): return self.get_drawing_area().get_size_request()
- def set_size(self, *args): self.get_drawing_area().set_size_request(*args)
- def get_scroll_pane(self): return self.drawing_area.get_parent()
+ def get_scroll_pane(self): return self.drawing_area.get_parent().get_parent()
def get_ctrl_mask(self): return self.drawing_area.ctrl_mask
def get_mod1_mask(self): return self.drawing_area.mod1_mask
def new_pixmap(self, *args): return self.get_drawing_area().new_pixmap(*args)
@@ -143,14 +146,11 @@ class FlowGraph(Element, _Flowgraph):
"""
id = self._get_unique_id(key)
#calculate the position coordinate
- W, H = self.get_size()
h_adj = self.get_scroll_pane().get_hadjustment()
v_adj = self.get_scroll_pane().get_vadjustment()
if coor is None: coor = (
- int(random.uniform(.25, .75) * min(h_adj.page_size, W) +
- h_adj.get_value()),
- int(random.uniform(.25, .75) * min(v_adj.page_size, H) +
- v_adj.get_value()),
+ int(random.uniform(.25, .75)*h_adj.get_page_size() + h_adj.get_value()),
+ int(random.uniform(.25, .75)*v_adj.get_page_size() + v_adj.get_value()),
)
#get the new block
block = self.new_block(key)
@@ -172,7 +172,8 @@ class FlowGraph(Element, _Flowgraph):
"""
#get selected blocks
blocks = self.get_selected_blocks()
- if not blocks: return None
+ if not blocks:
+ return None
#calc x and y min
x_min, y_min = blocks[0].get_coordinate()
for block in blocks:
@@ -180,10 +181,10 @@ class FlowGraph(Element, _Flowgraph):
x_min = min(x, x_min)
y_min = min(y, y_min)
#get connections between selected blocks
- connections = filter(
- lambda c: c.get_source().get_parent() in blocks and c.get_sink().get_parent() in blocks,
+ connections = list(filter(
+ lambda c: c.source_block in blocks and c.sink_block in blocks,
self.connections,
- )
+ ))
clipboard = (
(x_min, y_min),
[block.export_data() for block in blocks],
@@ -204,26 +205,26 @@ class FlowGraph(Element, _Flowgraph):
#recalc the position
h_adj = self.get_scroll_pane().get_hadjustment()
v_adj = self.get_scroll_pane().get_vadjustment()
- x_off = h_adj.get_value() - x_min + h_adj.page_size/4
- y_off = v_adj.get_value() - y_min + v_adj.page_size/4
+ x_off = h_adj.get_value() - x_min + h_adj.get_page_size() / 4
+ y_off = v_adj.get_value() - y_min + v_adj.get_page_size() / 4
if len(self.get_elements()) <= 1:
x_off, y_off = 0, 0
#create blocks
for block_n in blocks_n:
- block_key = block_n.find('key')
- if block_key == 'options': continue
+ block_key = block_n.get('key')
+ if block_key == 'options':
+ continue
block = self.new_block(block_key)
if not block:
continue # unknown block was pasted (e.g. dummy block)
selected.add(block)
#set params
- params = dict((n.find('key'), n.find('value'))
- for n in block_n.findall('param'))
+ params = {n['key']: n['value'] for n in block_n.get('param', [])}
if block_key == 'epy_block':
block.get_param('_io_cache').set_value(params.pop('_io_cache'))
block.get_param('_source_code').set_value(params.pop('_source_code'))
block.rewrite() # this creates the other params
- for param_key, param_value in params.iteritems():
+ for param_key, param_value in six.iteritems(params):
#setup id parameter
if param_key == 'id':
old_id2block[param_value] = block
@@ -238,8 +239,8 @@ class FlowGraph(Element, _Flowgraph):
self.update()
#create connections
for connection_n in connections_n:
- source = old_id2block[connection_n.find('source_block_id')].get_source(connection_n.find('source_key'))
- sink = old_id2block[connection_n.find('sink_block_id')].get_sink(connection_n.find('sink_key'))
+ source = old_id2block[connection_n.get('source_block_id')].get_source(connection_n.get('source_key'))
+ sink = old_id2block[connection_n.get('sink_block_id')].get_sink(connection_n.get('sink_key'))
self.connect(source, sink)
#set all pasted elements selected
for block in selected: selected = selected.union(set(block.get_connections()))
@@ -284,7 +285,8 @@ class FlowGraph(Element, _Flowgraph):
"""
changed = False
for selected_block in self.get_selected_blocks():
- if selected_block.set_enabled(enable): changed = True
+ if selected_block.set_enabled(enable):
+ changed = True
return changed
def bypass_selected(self):
@@ -298,7 +300,8 @@ class FlowGraph(Element, _Flowgraph):
"""
changed = False
for selected_block in self.get_selected_blocks():
- if selected_block.set_bypassed(): changed = True
+ if selected_block.set_bypassed():
+ changed = True
return changed
def move_selected(self, delta_coordinate):
@@ -309,9 +312,6 @@ class FlowGraph(Element, _Flowgraph):
delta_coordinate: the change in coordinates
"""
for selected_block in self.get_selected_blocks():
- delta_coordinate = selected_block.bound_move_delta(delta_coordinate)
-
- for selected_block in self.get_selected_blocks():
selected_block.move(delta_coordinate)
self.element_moved = True
@@ -400,52 +400,44 @@ class FlowGraph(Element, _Flowgraph):
changed = True
return changed
- def draw(self, gc, window):
+ def draw(self, widget, cr):
"""
Draw the background and grid if enabled.
- Draw all of the elements in this flow graph onto the pixmap.
- Draw the pixmap to the drawable window of this flow graph.
"""
-
- W,H = self.get_size()
- hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active()
- hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active()
-
- #draw the background
- gc.set_foreground(Colors.FLOWGRAPH_BACKGROUND_COLOR)
- window.draw_rectangle(gc, True, 0, 0, W, H)
-
# draw comments first
if Actions.TOGGLE_SHOW_BLOCK_COMMENTS.get_active():
for block in self.blocks:
- if hide_variables and (block.is_variable or block.is_import):
- continue # skip hidden disabled blocks and connections
if block.get_enabled():
- block.draw_comment(gc, window)
- #draw multi select rectangle
+ # block.draw_comment(widget, cr)
+ pass
+ # draw multi select rectangle
if self.mouse_pressed and (not self.get_selected_elements() or self.get_ctrl_mask()):
- #coordinates
x1, y1 = self.press_coor
x2, y2 = self.get_coordinate()
- #calculate top-left coordinate and width/height
x, y = int(min(x1, x2)), int(min(y1, y2))
w, h = int(abs(x1 - x2)), int(abs(y1 - y2))
- #draw
- gc.set_foreground(Colors.HIGHLIGHT_COLOR)
- window.draw_rectangle(gc, True, x, y, w, h)
- gc.set_foreground(Colors.BORDER_COLOR)
- window.draw_rectangle(gc, False, x, y, w, h)
- #draw blocks on top of connections
+ cr.set_source_rgb(*Colors.HIGHLIGHT_COLOR)
+ cr.rectangle(x, y, w, h)
+ cr.fill()
+ cr.set_source_rgb(*Colors.BORDER_COLOR)
+ cr.rectangle(x, y, w, h)
+ cr.stroke()
+
+ # draw blocks on top of connections
+ hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active()
+ hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active()
blocks = sorted(self.blocks, key=methodcaller('get_enabled'))
+
for element in chain(self.connections, blocks):
if hide_disabled_blocks and not element.get_enabled():
continue # skip hidden disabled blocks and connections
if hide_variables and (element.is_variable or element.is_import):
continue # skip hidden disabled blocks and connections
- element.draw(gc, window)
- #draw selected blocks on top of selected connections
+ element.draw(widget, cr)
+
+ # draw selected blocks on top of selected connections
for selected_element in self.get_selected_connections() + self.get_selected_blocks():
- selected_element.draw(gc, window)
+ selected_element.draw(widget, cr)
def update_selected(self):
"""
@@ -458,9 +450,9 @@ class FlowGraph(Element, _Flowgraph):
for selected in selected_elements:
if selected in elements: continue
selected_elements.remove(selected)
- if self._old_selected_port and self._old_selected_port.get_parent() not in elements:
+ if self._old_selected_port and self._old_selected_port.parent not in elements:
self._old_selected_port = None
- if self._new_selected_port and self._new_selected_port.get_parent() not in elements:
+ if self._new_selected_port and self._new_selected_port.parent not in elements:
self._new_selected_port = None
#update highlighting
for element in elements:
@@ -538,7 +530,7 @@ class FlowGraph(Element, _Flowgraph):
#update the selected port information
if selected_element.is_port:
if not coor_m: selected_port = selected_element
- selected_element = selected_element.get_parent()
+ selected_element = selected_element.parent_block
selected.add(selected_element)
#place at the end of the list
self.get_elements().remove(element)
@@ -670,7 +662,7 @@ class FlowGraph(Element, _Flowgraph):
self.mouse_pressed = True
self.update_selected_elements()
self.mouse_pressed = False
- self._context_menu.popup(None, None, None, event.button, event.time)
+ self._context_menu.popup(None, None, None, None, event.button, event.time)
def handle_mouse_selector_press(self, double_click, coordinate):
"""
@@ -681,11 +673,12 @@ class FlowGraph(Element, _Flowgraph):
"""
self.press_coor = coordinate
self.set_coordinate(coordinate)
- self.time = 0
self.mouse_pressed = True
- if double_click: self.unselect()
+
+ if double_click:
+ self.unselect()
self.update_selected_elements()
- #double click detected, bring up params dialog if possible
+
if double_click and self.get_selected_block():
self.mouse_pressed = False
Actions.BLOCK_PARAM_MODIFY()
@@ -697,69 +690,70 @@ class FlowGraph(Element, _Flowgraph):
And update the selected flowgraph elements.
"""
self.set_coordinate(coordinate)
- self.time = 0
self.mouse_pressed = False
if self.element_moved:
Actions.BLOCK_MOVE()
self.element_moved = False
self.update_selected_elements()
- def handle_mouse_motion(self, coordinate):
+ def handle_mouse_motion(self, coordinate, button1_pressed):
"""
The mouse has moved, respond to mouse dragging or notify elements
Move a selected element to the new coordinate.
Auto-scroll the scroll bars at the boundaries.
"""
- #to perform a movement, the mouse must be pressed
- # (no longer checking pending events via gtk.events_pending() - always true in Windows)
- if not self.mouse_pressed:
- # only continue if mouse-over stuff is enabled (just the auto-hide port label stuff for now)
- if not Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active(): return
- redraw = False
- for element in reversed(self.get_elements()):
- over_element = element.what_is_selected(coordinate)
- if not over_element: continue
- if over_element != self.element_under_mouse: # over sth new
- if self.element_under_mouse:
- redraw |= self.element_under_mouse.mouse_out() or False
- self.element_under_mouse = over_element
- redraw |= over_element.mouse_over() or False
- break
- else:
+ # to perform a movement, the mouse must be pressed
+ # (no longer checking pending events via Gtk.events_pending() - always true in Windows)
+ if not button1_pressed:
+ self._handle_mouse_motion_move(coordinate)
+ else:
+ self._handle_mouse_motion_drag(coordinate)
+
+ def _handle_mouse_motion_move(self, coordinate):
+ # only continue if mouse-over stuff is enabled (just the auto-hide port label stuff for now)
+ if not Actions.TOGGLE_AUTO_HIDE_PORT_LABELS.get_active():
+ return
+ redraw = False
+ for element in reversed(self.get_elements()):
+ over_element = element.what_is_selected(coordinate)
+ if not over_element:
+ continue
+ if over_element != self.element_under_mouse: # over sth new
if self.element_under_mouse:
redraw |= self.element_under_mouse.mouse_out() or False
- self.element_under_mouse = None
- if redraw:
- #self.create_labels()
- self.create_shapes()
- self.queue_draw()
+ self.element_under_mouse = over_element
+ redraw |= over_element.mouse_over() or False
+ break
else:
- #perform auto-scrolling
- width, height = self.get_size()
- x, y = coordinate
- h_adj = self.get_scroll_pane().get_hadjustment()
- v_adj = self.get_scroll_pane().get_vadjustment()
- for pos, length, adj, adj_val, adj_len in (
- (x, width, h_adj, h_adj.get_value(), h_adj.page_size),
- (y, height, v_adj, v_adj.get_value(), v_adj.page_size),
- ):
- #scroll if we moved near the border
- if pos-adj_val > adj_len-SCROLL_PROXIMITY_SENSITIVITY and adj_val+SCROLL_DISTANCE < length-adj_len:
- adj.set_value(adj_val+SCROLL_DISTANCE)
- adj.emit('changed')
- elif pos-adj_val < SCROLL_PROXIMITY_SENSITIVITY:
- adj.set_value(adj_val-SCROLL_DISTANCE)
- adj.emit('changed')
- #remove the connection if selected in drag event
- if len(self.get_selected_elements()) == 1 and self.get_selected_element().is_connection:
- Actions.ELEMENT_DELETE()
- #move the selected elements and record the new coordinate
- if not self.get_ctrl_mask():
- X, Y = self.get_coordinate()
- dX, dY = int(x - X), int(y - Y)
- active = Actions.TOGGLE_SNAP_TO_GRID.get_active() or self.get_mod1_mask()
- if not active or abs(dX) >= Utils.CANVAS_GRID_SIZE or abs(dY) >= Utils.CANVAS_GRID_SIZE:
- self.move_selected((dX, dY))
- self.set_coordinate((x, y))
- #queue draw for animation
+ if self.element_under_mouse:
+ redraw |= self.element_under_mouse.mouse_out() or False
+ self.element_under_mouse = None
+ if redraw:
+ # self.create_labels()
+ self.create_shapes()
self.queue_draw()
+
+ def _handle_mouse_motion_drag(self, coordinate):
+ # remove the connection if selected in drag event
+ if len(self.get_selected_elements()) == 1 and self.get_selected_element().is_connection:
+ Actions.ELEMENT_DELETE()
+
+ # move the selected elements and record the new coordinate
+ x, y = coordinate
+ if not self.get_ctrl_mask():
+ X, Y = self.get_coordinate()
+ dX, dY = int(x - X), int(y - Y)
+ active = Actions.TOGGLE_SNAP_TO_GRID.get_active() or self.get_mod1_mask()
+ if not active or abs(dX) >= Utils.CANVAS_GRID_SIZE or abs(dY) >= Utils.CANVAS_GRID_SIZE:
+ self.move_selected((dX, dY))
+ self.set_coordinate((x, y))
+ # queue draw for animation
+ self.queue_draw()
+
+ def get_max_coords(self, initial=(0, 0)):
+ w, h = initial
+ for block in self.blocks:
+ x, y = block.get_coordinate()
+ w = max(w, x + block.W)
+ h = max(h, y + block.H)
+ return w, h
diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py
index 1437391236..3236768969 100644
--- a/grc/gui/MainWindow.py
+++ b/grc/gui/MainWindow.py
@@ -17,9 +17,16 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+from __future__ import absolute_import
+
import os
-import gtk
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import GObject
+
from . import Bars, Actions, Preferences, Utils
from .BlockTreeWindow import BlockTreeWindow
@@ -31,38 +38,11 @@ from .NotebookPage import NotebookPage
from ..core import Messages
-MAIN_WINDOW_TITLE_TMPL = """\
-#if not $saved
-*#slurp
-#end if
-#if $basename
-$basename#slurp
-#else
-$new_flowgraph_title#slurp
-#end if
-#if $read_only
- (read only)#slurp
-#end if
-#if $dirname
- - $dirname#slurp
-#end if
- - $platform_name#slurp
-"""
-
-PAGE_TITLE_MARKUP_TMPL = """\
-#set $foreground = $saved and 'black' or 'red'
-<span foreground="$foreground">$encode($title or $new_flowgraph_title)</span>#slurp
-#if $read_only
- (ro)#slurp
-#end if
-"""
-
-
############################################################
# Main window
############################################################
-class MainWindow(gtk.Window):
+class MainWindow(Gtk.Window):
"""The topmost window with menus, the tool bar, and other major windows."""
# Constants the action handler can use to indicate which panel visibility to change.
@@ -87,23 +67,23 @@ class MainWindow(gtk.Window):
Preferences.load(platform)
# Setup window
- gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
- vbox = gtk.VBox()
+ GObject.GObject.__init__(self)
+ vbox = Gtk.VBox()
self.add(vbox)
# Create the menu bar and toolbar
self.add_accel_group(Actions.get_accel_group())
self.menu_bar = Bars.MenuBar(generate_modes, action_handler_callback)
- vbox.pack_start(self.menu_bar, False)
+ vbox.pack_start(self.menu_bar, False, False, 0)
self.tool_bar = Bars.Toolbar(generate_modes, action_handler_callback)
- vbox.pack_start(self.tool_bar, False)
+ vbox.pack_start(self.tool_bar, False, False, 0)
# Main parent container for the different panels
- self.container = gtk.HPaned()
- vbox.pack_start(self.container)
+ self.main = Gtk.HPaned() #(orientation=Gtk.Orientation.HORIZONTAL)
+ vbox.pack_start(self.main, True, True, 0)
# Create the notebook
- self.notebook = gtk.Notebook()
+ self.notebook = Gtk.Notebook()
self.page_to_be_closed = None
self.current_page = None
self.notebook.set_show_border(False)
@@ -112,19 +92,22 @@ class MainWindow(gtk.Window):
# Create the console window
self.text_display = TextDisplay()
- self.console_window = gtk.ScrolledWindow()
- self.console_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.console_window = Gtk.ScrolledWindow()
+ self.console_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self.console_window.add(self.text_display)
self.console_window.set_size_request(-1, DEFAULT_CONSOLE_WINDOW_WIDTH)
# Create the block tree and variable panels
- self.btwin = BlockTreeWindow(platform, self.get_flow_graph)
- self.vars = VariableEditor(platform, self.get_flow_graph)
+ self.btwin = BlockTreeWindow(platform)
+ self.btwin.connect('create_new_block', self._add_block_to_current_flow_graph)
+ self.vars = VariableEditor()
+ self.vars.connect('create_new_block', self._add_block_to_current_flow_graph)
+ self.vars.connect('remove_block', self._remove_block_from_current_flow_graph)
# Figure out which place to put the variable editor
- self.left = gtk.VPaned()
- self.right = gtk.VPaned()
- self.left_subpanel = gtk.HPaned()
+ self.left = Gtk.VPaned() #orientation=Gtk.Orientation.VERTICAL)
+ self.right = Gtk.VPaned() #orientation=Gtk.Orientation.VERTICAL)
+ self.left_subpanel = Gtk.HPaned() #orientation=Gtk.Orientation.HORIZONTAL)
self.variable_panel_sidebar = Preferences.variable_editor_sidebar()
if self.variable_panel_sidebar:
@@ -142,12 +125,12 @@ class MainWindow(gtk.Window):
# Create the right panel
self.right.pack1(self.btwin)
- self.container.pack1(self.left)
- self.container.pack2(self.right, False)
+ self.main.pack1(self.left)
+ self.main.pack2(self.right, False)
- # load preferences and show the main window
+ # Load preferences and show the main window
self.resize(*Preferences.main_window_size())
- self.container.set_position(Preferences.blocks_window_position())
+ self.main.set_position(Preferences.blocks_window_position())
self.left.set_position(Preferences.console_window_position())
if self.variable_panel_sidebar:
self.right.set_position(Preferences.variable_editor_position(sidebar=True))
@@ -163,6 +146,13 @@ class MainWindow(gtk.Window):
# Event Handlers
############################################################
+ def _add_block_to_current_flow_graph(self, widget, key):
+ self.current_flow_graph.add_new_block(key)
+
+ def _remove_block_from_current_flow_graph(self, widget, key):
+ block = self.current_flow_graph.get_block(key)
+ self.current_flow_graph.remove_element(block)
+
def _quit(self, window, event):
"""
Handle the delete event from the main window.
@@ -263,17 +253,15 @@ class MainWindow(gtk.Window):
file_path=file_path,
)
if file_path: Messages.send_end_load()
- except Exception, e: #return on failure
+ except Exception as e: #return on failure
Messages.send_fail_load(e)
if isinstance(e, KeyError) and str(e) == "'options'":
# This error is unrecoverable, so crash gracefully
exit(-1)
return
#add this page to the notebook
- self.notebook.append_page(page, page.get_tab())
- try: self.notebook.set_tab_reorderable(page, True)
- except: pass #gtk too old
- self.notebook.set_tab_label_packing(page, False, False, gtk.PACK_START)
+ self.notebook.append_page(page, page.tab)
+ self.notebook.set_tab_reorderable(page, True)
#only show if blank or manual
if not file_path or show: self._set_page(page)
@@ -284,10 +272,10 @@ class MainWindow(gtk.Window):
Returns:
true if all closed
"""
- open_files = filter(lambda file: file, self._get_files()) #filter blank files
- open_file = self.get_page().get_file_path()
+ open_files = [file for file in self._get_files() if file] #filter blank files
+ open_file = self.current_page.file_path
#close each page
- for page in sorted(self.get_pages(), key=lambda p: p.get_saved()):
+ for page in sorted(self.get_pages(), key=lambda p: p.saved):
self.page_to_be_closed = page
closed = self.close_page(False)
if not closed:
@@ -298,7 +286,7 @@ class MainWindow(gtk.Window):
Preferences.file_open(open_file)
Preferences.main_window_size(self.get_size())
Preferences.console_window_position(self.left.get_position())
- Preferences.blocks_window_position(self.container.get_position())
+ Preferences.blocks_window_position(self.main.get_position())
if self.variable_panel_sidebar:
Preferences.variable_editor_position(self.right.get_position(), sidebar=True)
else:
@@ -315,23 +303,24 @@ class MainWindow(gtk.Window):
Args:
ensure: boolean
"""
- if not self.page_to_be_closed: self.page_to_be_closed = self.get_page()
+ if not self.page_to_be_closed: self.page_to_be_closed = self.current_page
#show the page if it has an executing flow graph or is unsaved
- if self.page_to_be_closed.get_proc() or not self.page_to_be_closed.get_saved():
+ if self.page_to_be_closed.process or not self.page_to_be_closed.saved:
self._set_page(self.page_to_be_closed)
#unsaved? ask the user
- if not self.page_to_be_closed.get_saved():
+ if not self.page_to_be_closed.saved:
response = self._save_changes() # return value is either OK, CLOSE, or CANCEL
- if response == gtk.RESPONSE_OK:
+ if response == Gtk.ResponseType.OK:
Actions.FLOW_GRAPH_SAVE() #try to save
- if not self.page_to_be_closed.get_saved(): #still unsaved?
+ if not self.page_to_be_closed.saved: #still unsaved?
self.page_to_be_closed = None #set the page to be closed back to None
return False
- elif response == gtk.RESPONSE_CANCEL:
+ elif response == Gtk.ResponseType.CANCEL:
self.page_to_be_closed = None
return False
#stop the flow graph if executing
- if self.page_to_be_closed.get_proc(): Actions.FLOW_GRAPH_KILL()
+ if self.page_to_be_closed.process:
+ Actions.FLOW_GRAPH_KILL()
#remove the page
self.notebook.remove_page(self.notebook.page_num(self.page_to_be_closed))
if ensure and self.notebook.get_n_pages() == 0: self.new_page() #no pages, make a new one
@@ -347,69 +336,49 @@ class MainWindow(gtk.Window):
Set the title of the main window.
Set the titles on the page tabs.
Show/hide the console window.
-
- Args:
- title: the window title
"""
- gtk.Window.set_title(self, Utils.parse_template(MAIN_WINDOW_TITLE_TMPL,
- basename=os.path.basename(self.get_page().get_file_path()),
- dirname=os.path.dirname(self.get_page().get_file_path()),
- new_flowgraph_title=NEW_FLOGRAPH_TITLE,
- read_only=self.get_page().get_read_only(),
- saved=self.get_page().get_saved(),
- platform_name=self._platform.config.name,
- )
- )
- #set tab titles
- for page in self.get_pages(): page.set_markup(
- Utils.parse_template(PAGE_TITLE_MARKUP_TMPL,
- #get filename and strip out file extension
- title=os.path.splitext(os.path.basename(page.get_file_path()))[0],
- read_only=page.get_read_only(), saved=page.get_saved(),
- new_flowgraph_title=NEW_FLOGRAPH_TITLE,
- )
- )
- #show/hide notebook tabs
+ page = self.current_page
+
+ basename = os.path.basename(page.file_path)
+ dirname = os.path.dirname(page.file_path)
+ Gtk.Window.set_title(self, ''.join((
+ '*' if not page.saved else '', basename if basename else NEW_FLOGRAPH_TITLE,
+ '(read only)' if page.get_read_only() else '', ' - ',
+ dirname if dirname else self._platform.config.name,
+ )))
+ # set tab titles
+ for page in self.get_pages():
+ file_name = os.path.splitext(os.path.basename(page.file_path))[0]
+ page.set_markup('<span foreground="{foreground}">{title}{ro}</span>'.format(
+ foreground='black' if page.saved else 'red', ro=' (ro)' if page.get_read_only() else '',
+ title=Utils.encode(file_name or NEW_FLOGRAPH_TITLE),
+ ))
+ # show/hide notebook tabs
self.notebook.set_show_tabs(len(self.get_pages()) > 1)
- # Need to update the variable window when changing
- self.vars.update_gui()
+ # Need to update the variable window when changing
+ self.vars.update_gui(self.current_flow_graph.blocks)
def update_pages(self):
"""
Forces a reload of all the pages in this notebook.
"""
for page in self.get_pages():
- success = page.get_flow_graph().reload()
+ success = page.flow_graph.reload()
if success: # Only set saved if errors occurred during import
- page.set_saved(False)
+ page.saved = False
- def get_page(self):
- """
- Get the selected page.
-
- Returns:
- the selected page
- """
- return self.current_page
-
- def get_flow_graph(self):
- """
- Get the selected flow graph.
-
- Returns:
- the selected flow graph
- """
- return self.get_page().get_flow_graph()
+ @property
+ def current_flow_graph(self):
+ return self.current_page.flow_graph
def get_focus_flag(self):
"""
Get the focus flag from the current page.
-
Returns:
the focus flag
"""
- return self.get_page().get_drawing_area().get_focus_flag()
+ return self.current_page.get_drawing_area().get_focus_flag()
############################################################
# Helpers
@@ -433,13 +402,13 @@ class MainWindow(gtk.Window):
the response_id (see buttons variable below)
"""
buttons = (
- 'Close without saving', gtk.RESPONSE_CLOSE,
- gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
- gtk.STOCK_SAVE, gtk.RESPONSE_OK
+ 'Close without saving', Gtk.ResponseType.CLOSE,
+ Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
+ Gtk.STOCK_SAVE, Gtk.ResponseType.OK
)
return MessageDialogHelper(
- gtk.MESSAGE_QUESTION, gtk.BUTTONS_NONE, 'Unsaved Changes!',
- 'Would you like to save changes before closing?', gtk.RESPONSE_OK, buttons
+ Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, 'Unsaved Changes!',
+ 'Would you like to save changes before closing?', Gtk.ResponseType.OK, buttons
)
def _get_files(self):
@@ -449,7 +418,7 @@ class MainWindow(gtk.Window):
Returns:
list of file paths
"""
- return map(lambda page: page.get_file_path(), self.get_pages())
+ return [page.file_path for page in self.get_pages()]
def get_pages(self):
"""
@@ -458,4 +427,5 @@ class MainWindow(gtk.Window):
Returns:
list of pages
"""
- return [self.notebook.get_nth_page(page_num) for page_num in range(self.notebook.get_n_pages())]
+ return [self.notebook.get_nth_page(page_num)
+ for page_num in range(self.notebook.get_n_pages())]
diff --git a/grc/gui/NotebookPage.py b/grc/gui/NotebookPage.py
index c9e8d0f186..4745035aff 100644
--- a/grc/gui/NotebookPage.py
+++ b/grc/gui/NotebookPage.py
@@ -17,17 +17,18 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import pygtk
-pygtk.require('2.0')
-import gtk
-import Actions
-from StateCache import StateCache
-from Constants import MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT
-from DrawingArea import DrawingArea
+from __future__ import absolute_import
import os
+from gi.repository import Gtk, Gdk, GObject
-class NotebookPage(gtk.HBox):
+from . import Actions
+from .StateCache import StateCache
+from .Constants import MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT
+from .DrawingArea import DrawingArea
+
+
+class NotebookPage(Gtk.HBox):
"""A page in the notebook."""
def __init__(self, main_window, flow_graph, file_path=''):
@@ -38,59 +39,53 @@ class NotebookPage(gtk.HBox):
main_window: main window
file_path: path to a flow graph file
"""
- self._flow_graph = flow_graph
- self.process = None
- #import the file
+ Gtk.HBox.__init__(self)
+
self.main_window = main_window
+ self.flow_graph = flow_graph
self.file_path = file_path
- initial_state = flow_graph.get_parent().parse_flow_graph(file_path)
- self.state_cache = StateCache(initial_state)
+
+ self.process = None
self.saved = True
- #import the data to the flow graph
- self.get_flow_graph().import_data(initial_state)
- #initialize page gui
- gtk.HBox.__init__(self, False, 0)
- self.show()
- #tab box to hold label and close button
- self.tab = gtk.HBox(False, 0)
- #setup tab label
- self.label = gtk.Label()
- self.tab.pack_start(self.label, False)
- #setup button image
- image = gtk.Image()
- image.set_from_stock('gtk-close', gtk.ICON_SIZE_MENU)
- #setup image box
- image_box = gtk.HBox(False, 0)
+
+ # import the file
+ initial_state = flow_graph.parent_platform.parse_flow_graph(file_path)
+ flow_graph.import_data(initial_state)
+ self.state_cache = StateCache(initial_state)
+
+ # tab box to hold label and close button
+ self.label = Gtk.Label()
+ image = Gtk.Image.new_from_icon_name('window-close', Gtk.IconSize.MENU)
+ image_box = Gtk.HBox(homogeneous=False, spacing=0)
image_box.pack_start(image, True, False, 0)
- #setup the button
- button = gtk.Button()
+ button = Gtk.Button()
button.connect("clicked", self._handle_button)
- button.set_relief(gtk.RELIEF_NONE)
+ button.set_relief(Gtk.ReliefStyle.NONE)
button.add(image_box)
- #button size
- w, h = gtk.icon_size_lookup_for_settings(button.get_settings(), gtk.ICON_SIZE_MENU)
- button.set_size_request(w+6, h+6)
- self.tab.pack_start(button, False)
- self.tab.show_all()
- #setup scroll window and drawing area
- self.scrolled_window = gtk.ScrolledWindow()
+
+ tab = self.tab = Gtk.HBox(homogeneous=False, spacing=0)
+ tab.pack_start(self.label, False, False, 0)
+ tab.pack_start(button, False, False, 0)
+ tab.show_all()
+
+ # setup scroll window and drawing area
+ self.drawing_area = DrawingArea(flow_graph)
+ flow_graph.drawing_area = self.drawing_area
+
+ self.scrolled_window = Gtk.ScrolledWindow()
self.scrolled_window.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
- self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.scrolled_window.set_policy(Gtk.PolicyType.ALWAYS, Gtk.PolicyType.ALWAYS)
self.scrolled_window.connect('key-press-event', self._handle_scroll_window_key_press)
- self.drawing_area = DrawingArea(self.get_flow_graph())
- self.scrolled_window.add_with_viewport(self.get_drawing_area())
- self.pack_start(self.scrolled_window)
- #inject drawing area into flow graph
- self.get_flow_graph().drawing_area = self.get_drawing_area()
- self.show_all()
+ self.scrolled_window.add_with_viewport(self.drawing_area)
+ self.pack_start(self.scrolled_window, True, True, 0)
- def get_drawing_area(self): return self.drawing_area
+ self.show_all()
def _handle_scroll_window_key_press(self, widget, event):
"""forward Ctrl-PgUp/Down to NotebookPage (switch fg instead of horiz. scroll"""
is_ctrl_pg = (
- event.state & gtk.gdk.CONTROL_MASK and
- event.keyval in (gtk.keysyms.Page_Up, gtk.keysyms.Page_Down)
+ event.state & Gdk.ModifierType.CONTROL_MASK and
+ event.keyval in (Gdk.KEY_Page_Up, Gdk.KEY_Page_Down)
)
if is_ctrl_pg:
return self.get_parent().event(event)
@@ -102,8 +97,8 @@ class NotebookPage(gtk.HBox):
Returns:
generator
"""
- platform = self.get_flow_graph().get_parent()
- return platform.Generator(self.get_flow_graph(), self.get_file_path())
+ platform = self.flow_graph.parent_platform
+ return platform.Generator(self.flow_graph, self.file_path)
def _handle_button(self, button):
"""
@@ -125,42 +120,6 @@ class NotebookPage(gtk.HBox):
"""
self.label.set_markup(markup)
- def get_tab(self):
- """
- Get the gtk widget for this page's tab.
-
- Returns:
- gtk widget
- """
- return self.tab
-
- def get_proc(self):
- """
- Get the subprocess for the flow graph.
-
- Returns:
- the subprocess object
- """
- return self.process
-
- def set_proc(self, process):
- """
- Set the subprocess object.
-
- Args:
- process: the new subprocess
- """
- self.process = process
-
- def get_flow_graph(self):
- """
- Get the flow graph.
-
- Returns:
- the flow graph
- """
- return self._flow_graph
-
def get_read_only(self):
"""
Get the read-only state of the file.
@@ -169,51 +128,7 @@ class NotebookPage(gtk.HBox):
Returns:
true for read-only
"""
- if not self.get_file_path(): return False
- return os.path.exists(self.get_file_path()) and \
- not os.access(self.get_file_path(), os.W_OK)
-
- def get_file_path(self):
- """
- Get the file path for the flow graph.
-
- Returns:
- the file path or ''
- """
- return self.file_path
-
- def set_file_path(self, file_path=''):
- """
- Set the file path, '' for no file path.
-
- Args:
- file_path: file path string
- """
- self.file_path = os.path.abspath(file_path) if file_path else ''
-
- def get_saved(self):
- """
- Get the saved status for the flow graph.
-
- Returns:
- true if saved
- """
- return self.saved
-
- def set_saved(self, saved=True):
- """
- Set the saved status.
-
- Args:
- saved: boolean status
- """
- self.saved = saved
-
- def get_state_cache(self):
- """
- Get the state cache for the flow graph.
-
- Returns:
- the state cache
- """
- return self.state_cache
+ if not self.file_path:
+ return False
+ return (os.path.exists(self.file_path) and
+ not os.access(self.file_path, os.W_OK))
diff --git a/grc/gui/Param.py b/grc/gui/Param.py
index 4b5a3c294a..a630f5faa3 100644
--- a/grc/gui/Param.py
+++ b/grc/gui/Param.py
@@ -1,396 +1,27 @@
-"""
-Copyright 2007-2011 Free Software Foundation, Inc.
-This file is part of GNU Radio
-
-GNU Radio Companion 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 2
-of the License, or (at your option) any later version.
-
-GNU Radio Companion is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-"""
-
-import os
-
-import pygtk
-pygtk.require('2.0')
-import gtk
-
-from . import Colors, Utils, Constants
-from .Element import Element
-
-from ..core.Param import Param as _Param
-
-
-class InputParam(gtk.HBox):
- """The base class for an input parameter inside the input parameters dialog."""
- expand = False
-
- def __init__(self, param, changed_callback=None, editing_callback=None):
- gtk.HBox.__init__(self)
- self.param = param
- self._changed_callback = changed_callback
- self._editing_callback = editing_callback
- self.label = gtk.Label() #no label, markup is added by set_markup
- self.label.set_size_request(150, -1)
- self.pack_start(self.label, False)
- self.set_markup = lambda m: self.label.set_markup(m)
- self.tp = None
- self._have_pending_changes = False
- #connect events
- self.connect('show', self._update_gui)
-
- def set_color(self, color):
- pass
-
- def set_tooltip_text(self, text):
- pass
-
- def get_text(self):
- raise NotImplementedError()
-
- def _update_gui(self, *args):
- """
- Set the markup, color, tooltip, show/hide.
- """
- #set the markup
- has_cb = \
- hasattr(self.param.get_parent(), 'get_callbacks') and \
- filter(lambda c: self.param.get_key() in c, self.param.get_parent()._callbacks)
- self.set_markup(Utils.parse_template(PARAM_LABEL_MARKUP_TMPL,
- param=self.param, has_cb=has_cb,
- modified=self._have_pending_changes))
- #set the color
- self.set_color(self.param.get_color())
- #set the tooltip
- self.set_tooltip_text(
- Utils.parse_template(TIP_MARKUP_TMPL, param=self.param).strip(),
- )
- #show/hide
- if self.param.get_hide() == 'all': self.hide_all()
- else: self.show_all()
-
- def _mark_changed(self, *args):
- """
- Mark this param as modified on change, but validate only on focus-lost
- """
- self._have_pending_changes = True
- self._update_gui()
- if self._editing_callback:
- self._editing_callback(self, None)
-
- def _apply_change(self, *args):
- """
- Handle a gui change by setting the new param value,
- calling the callback (if applicable), and updating.
- """
- #set the new value
- self.param.set_value(self.get_text())
- #call the callback
- if self._changed_callback:
- self._changed_callback(self, None)
- else:
- self.param.validate()
- #gui update
- self._have_pending_changes = False
- self._update_gui()
-
- def _handle_key_press(self, widget, event):
- if event.keyval == gtk.keysyms.Return and event.state & gtk.gdk.CONTROL_MASK:
- self._apply_change(widget, event)
- return True
- return False
-
- def apply_pending_changes(self):
- if self._have_pending_changes:
- self._apply_change()
-
-
-class EntryParam(InputParam):
- """Provide an entry box for strings and numbers."""
-
- def __init__(self, *args, **kwargs):
- InputParam.__init__(self, *args, **kwargs)
- self._input = gtk.Entry()
- self._input.set_text(self.param.get_value())
- self._input.connect('changed', self._mark_changed)
- self._input.connect('focus-out-event', self._apply_change)
- self._input.connect('key-press-event', self._handle_key_press)
- self.pack_start(self._input, True)
-
- def get_text(self):
- return self._input.get_text()
-
- def set_color(self, color):
- need_status_color = self.label not in self.get_children()
- text_color = (
- Colors.PARAM_ENTRY_TEXT_COLOR if not need_status_color else
- gtk.gdk.color_parse('blue') if self._have_pending_changes else
- gtk.gdk.color_parse('red') if not self.param.is_valid() else
- Colors.PARAM_ENTRY_TEXT_COLOR)
- base_color = (
- Colors.BLOCK_DISABLED_COLOR
- if need_status_color and not self.param.get_parent().get_enabled()
- else gtk.gdk.color_parse(color)
- )
- self._input.modify_base(gtk.STATE_NORMAL, base_color)
- self._input.modify_text(gtk.STATE_NORMAL, text_color)
-
- def set_tooltip_text(self, text):
- try:
- self._input.set_tooltip_text(text)
- except AttributeError:
- pass # no tooltips for old GTK
-
-
-class MultiLineEntryParam(InputParam):
- """Provide an multi-line box for strings."""
- expand = True
-
- def __init__(self, *args, **kwargs):
- InputParam.__init__(self, *args, **kwargs)
- self._buffer = gtk.TextBuffer()
- self._buffer.set_text(self.param.get_value())
- self._buffer.connect('changed', self._mark_changed)
-
- self._view = gtk.TextView(self._buffer)
- self._view.connect('focus-out-event', self._apply_change)
- self._view.connect('key-press-event', self._handle_key_press)
-
- self._sw = gtk.ScrolledWindow()
- self._sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- self._sw.add_with_viewport(self._view)
-
- self.pack_start(self._sw, True)
-
- def get_text(self):
- buf = self._buffer
- return buf.get_text(buf.get_start_iter(),
- buf.get_end_iter()).strip()
-
- def set_color(self, color):
- self._view.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
- self._view.modify_text(gtk.STATE_NORMAL, Colors.PARAM_ENTRY_TEXT_COLOR)
-
- def set_tooltip_text(self, text):
- try:
- self._view.set_tooltip_text(text)
- except AttributeError:
- pass # no tooltips for old GTK
-
-
-# try:
-# import gtksourceview
-# lang_manager = gtksourceview.SourceLanguagesManager()
-# py_lang = lang_manager.get_language_from_mime_type('text/x-python')
+# Copyright 2007-2016 Free Software Foundation, Inc.
+# This file is part of GNU Radio
#
-# class PythonEditorParam(InputParam):
-# expand = True
+# GNU Radio Companion 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 2
+# of the License, or (at your option) any later version.
#
-# def __init__(self, *args, **kwargs):
-# InputParam.__init__(self, *args, **kwargs)
+# GNU Radio Companion is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# buf = self._buffer = gtksourceview.SourceBuffer()
-# buf.set_language(py_lang)
-# buf.set_highlight(True)
-# buf.set_text(self.param.get_value())
-# buf.connect('changed', self._mark_changed)
-#
-# view = self._view = gtksourceview.SourceView(self._buffer)
-# view.connect('focus-out-event', self._apply_change)
-# view.connect('key-press-event', self._handle_key_press)
-# view.set_tabs_width(4)
-# view.set_insert_spaces_instead_of_tabs(True)
-# view.set_auto_indent(True)
-# view.set_border_width(2)
-#
-# scroll = gtk.ScrolledWindow()
-# scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
-# scroll.add_with_viewport(view)
-# self.pack_start(scroll, True)
-#
-# def get_text(self):
-# buf = self._buffer
-# return buf.get_text(buf.get_start_iter(),
-# buf.get_end_iter()).strip()
-#
-# except ImportError:
-# print "Package 'gtksourceview' not found. No Syntax highlighting."
-# PythonEditorParam = MultiLineEntryParam
-
-class PythonEditorParam(InputParam):
-
- def __init__(self, *args, **kwargs):
- InputParam.__init__(self, *args, **kwargs)
- button = self._button = gtk.Button('Open in Editor')
- button.connect('clicked', self.open_editor)
- self.pack_start(button, True)
-
- def open_editor(self, widget=None):
- flowgraph = self.param.get_parent().get_parent()
- flowgraph.install_external_editor(self.param)
-
- def get_text(self):
- pass # we never update the value from here
-
- def set_color(self, color):
- # self._button.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
- self._button.modify_text(gtk.STATE_NORMAL, Colors.PARAM_ENTRY_TEXT_COLOR)
-
- def _apply_change(self, *args):
- pass
-
-
-class EnumParam(InputParam):
- """Provide an entry box for Enum types with a drop down menu."""
-
- def __init__(self, *args, **kwargs):
- InputParam.__init__(self, *args, **kwargs)
- self._input = gtk.combo_box_new_text()
- for option in self.param.get_options(): self._input.append_text(option.get_name())
- self._input.set_active(self.param.get_option_keys().index(self.param.get_value()))
- self._input.connect('changed', self._editing_callback)
- self._input.connect('changed', self._apply_change)
- self.pack_start(self._input, False)
-
- def get_text(self):
- return self.param.get_option_keys()[self._input.get_active()]
-
- def set_tooltip_text(self, text):
- try:
- self._input.set_tooltip_text(text)
- except AttributeError:
- pass # no tooltips for old GTK
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+from __future__ import absolute_import
+from . import Utils, Constants
-class EnumEntryParam(InputParam):
- """Provide an entry box and drop down menu for Raw Enum types."""
-
- def __init__(self, *args, **kwargs):
- InputParam.__init__(self, *args, **kwargs)
- self._input = gtk.combo_box_entry_new_text()
- for option in self.param.get_options(): self._input.append_text(option.get_name())
- try: self._input.set_active(self.param.get_option_keys().index(self.param.get_value()))
- except:
- self._input.set_active(-1)
- self._input.get_child().set_text(self.param.get_value())
- self._input.connect('changed', self._apply_change)
- self._input.get_child().connect('changed', self._mark_changed)
- self._input.get_child().connect('focus-out-event', self._apply_change)
- self._input.get_child().connect('key-press-event', self._handle_key_press)
- self.pack_start(self._input, False)
-
- def get_text(self):
- if self._input.get_active() == -1: return self._input.get_child().get_text()
- return self.param.get_option_keys()[self._input.get_active()]
-
- def set_tooltip_text(self, text):
- try:
- if self._input.get_active() == -1: #custom entry
- self._input.get_child().set_tooltip_text(text)
- else:
- self._input.set_tooltip_text(text)
- except AttributeError:
- pass # no tooltips for old GTK
-
- def set_color(self, color):
- if self._input.get_active() == -1: #custom entry, use color
- self._input.get_child().modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
- self._input.get_child().modify_text(gtk.STATE_NORMAL, Colors.PARAM_ENTRY_TEXT_COLOR)
- else: #from enum, make pale background
- self._input.get_child().modify_base(gtk.STATE_NORMAL, Colors.ENTRYENUM_CUSTOM_COLOR)
- self._input.get_child().modify_text(gtk.STATE_NORMAL, Colors.PARAM_ENTRY_TEXT_COLOR)
-
-
-class FileParam(EntryParam):
- """Provide an entry box for filename and a button to browse for a file."""
-
- def __init__(self, *args, **kwargs):
- EntryParam.__init__(self, *args, **kwargs)
- input = gtk.Button('...')
- input.connect('clicked', self._handle_clicked)
- self.pack_start(input, False)
-
- def _handle_clicked(self, widget=None):
- """
- If the button was clicked, open a file dialog in open/save format.
- Replace the text in the entry with the new filename from the file dialog.
- """
- #get the paths
- file_path = self.param.is_valid() and self.param.get_evaluated() or ''
- (dirname, basename) = os.path.isfile(file_path) and os.path.split(file_path) or (file_path, '')
- # check for qss theme default directory
- if self.param.get_key() == 'qt_qss_theme':
- dirname = os.path.dirname(dirname) # trim filename
- if not os.path.exists(dirname):
- platform = self.param.get_parent().get_parent().get_parent()
- dirname = os.path.join(platform.config.install_prefix,
- '/share/gnuradio/themes')
- if not os.path.exists(dirname):
- dirname = os.getcwd() # fix bad paths
-
- #build the dialog
- if self.param.get_type() == 'file_open':
- file_dialog = gtk.FileChooserDialog('Open a Data File...', None,
- gtk.FILE_CHOOSER_ACTION_OPEN, ('gtk-cancel',gtk.RESPONSE_CANCEL,'gtk-open',gtk.RESPONSE_OK))
- elif self.param.get_type() == 'file_save':
- file_dialog = gtk.FileChooserDialog('Save a Data File...', None,
- gtk.FILE_CHOOSER_ACTION_SAVE, ('gtk-cancel',gtk.RESPONSE_CANCEL, 'gtk-save',gtk.RESPONSE_OK))
- file_dialog.set_do_overwrite_confirmation(True)
- file_dialog.set_current_name(basename) #show the current filename
- else:
- raise ValueError("Can't open file chooser dialog for type " + repr(self.param.get_type()))
- file_dialog.set_current_folder(dirname) #current directory
- file_dialog.set_select_multiple(False)
- file_dialog.set_local_only(True)
- if gtk.RESPONSE_OK == file_dialog.run(): #run the dialog
- file_path = file_dialog.get_filename() #get the file path
- self._input.set_text(file_path)
- self._editing_callback()
- self._apply_change()
- file_dialog.destroy() #destroy the dialog
-
-
-PARAM_MARKUP_TMPL="""\
-#set $foreground = $param.is_valid() and 'black' or 'red'
-<span foreground="$foreground" font_desc="$font"><b>$encode($param.get_name()): </b>$encode(repr($param).replace('\\n',' '))</span>"""
-
-PARAM_LABEL_MARKUP_TMPL="""\
-#set $foreground = $modified and 'blue' or $param.is_valid() and 'black' or 'red'
-#set $underline = $has_cb and 'low' or 'none'
-<span underline="$underline" foreground="$foreground" font_desc="Sans 9">$encode($param.get_name())</span>"""
+from . import ParamWidgets
+from .Element import Element
-TIP_MARKUP_TMPL="""\
-########################################
-#def truncate(string)
- #set $max_len = 100
- #set $string = str($string)
- #if len($string) > $max_len
-$('%s...%s'%($string[:$max_len/2], $string[-$max_len/2:]))#slurp
- #else
-$string#slurp
- #end if
-#end def
-########################################
-Key: $param.get_key()
-Type: $param.get_type()
-#if $param.is_valid()
-Value: $truncate($param.get_evaluated())
-#elif len($param.get_error_messages()) == 1
-Error: $(param.get_error_messages()[0])
-#else
-Error:
- #for $error_msg in $param.get_error_messages()
- * $error_msg
- #end for
-#end if"""
+from ..core.Param import Param as _Param
class Param(Element, _Param):
@@ -411,31 +42,63 @@ class Param(Element, _Param):
gtk input class
"""
if self.get_type() in ('file_open', 'file_save'):
- input_widget = FileParam(self, *args, **kwargs)
+ input_widget_cls = ParamWidgets.FileParam
elif self.is_enum():
- input_widget = EnumParam(self, *args, **kwargs)
+ input_widget_cls = ParamWidgets.EnumParam
elif self.get_options():
- input_widget = EnumEntryParam(self, *args, **kwargs)
+ input_widget_cls = ParamWidgets.EnumEntryParam
elif self.get_type() == '_multiline':
- input_widget = MultiLineEntryParam(self, *args, **kwargs)
+ input_widget_cls = ParamWidgets.MultiLineEntryParam
elif self.get_type() == '_multiline_python_external':
- input_widget = PythonEditorParam(self, *args, **kwargs)
+ input_widget_cls = ParamWidgets.PythonEditorParam
else:
- input_widget = EntryParam(self, *args, **kwargs)
-
- return input_widget
+ input_widget_cls = ParamWidgets.EntryParam
+
+ return input_widget_cls(self, *args, **kwargs)
+
+ def format_label_markup(self, have_pending_changes=False):
+ block = self.parent
+ # fixme: using non-public attribute here
+ has_callback = \
+ hasattr(block, 'get_callbacks') and \
+ any(self.get_key() in callback for callback in block._callbacks)
+
+ return '<span underline="{line}" foreground="{color}" font_desc="Sans 9">{label}</span>'.format(
+ line='low' if has_callback else 'none',
+ color='blue' if have_pending_changes else
+ 'black' if self.is_valid() else
+ 'red',
+ label=Utils.encode(self.get_name())
+ )
- def get_markup(self):
+ def format_tooltip_text(self):
+ errors = self.get_error_messages()
+ tooltip_lines = ['Key: ' + self.get_key(), 'Type: ' + self.get_type()]
+ if self.is_valid():
+ value = str(self.get_evaluated())
+ if len(value) > 100:
+ value = '{}...{}'.format(value[:50], value[-50:])
+ tooltip_lines.append('Value: ' + value)
+ elif len(errors) == 1:
+ tooltip_lines.append('Error: ' + errors[0])
+ elif len(errors) > 1:
+ tooltip_lines.append('Error:')
+ tooltip_lines.extend(' * ' + msg for msg in errors)
+ return '\n'.join(tooltip_lines)
+
+ def format_block_surface_markup(self):
"""
Get the markup for this param.
Returns:
a pango markup string
"""
- return Utils.parse_template(PARAM_MARKUP_TMPL,
- param=self, font=Constants.PARAM_FONT)
+ return '<span foreground="{color}" font_desc="{font}"><b>{label}:</b> {value}</span>'.format(
+ color='black' if self.is_valid() else 'red', font=Constants.PARAM_FONT,
+ label=Utils.encode(self.get_name()), value=Utils.encode(repr(self).replace('\n', ' '))
+ )
diff --git a/grc/gui/ParamWidgets.py b/grc/gui/ParamWidgets.py
new file mode 100644
index 0000000000..fbbfa93d1d
--- /dev/null
+++ b/grc/gui/ParamWidgets.py
@@ -0,0 +1,300 @@
+# Copyright 2007-2016 Free Software Foundation, Inc.
+# This file is part of GNU Radio
+#
+# GNU Radio Companion 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 2
+# of the License, or (at your option) any later version.
+#
+# GNU Radio Companion is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+from __future__ import absolute_import
+import os
+
+from gi.repository import Gtk, Gdk
+
+from . import Colors
+
+
+class InputParam(Gtk.HBox):
+ """The base class for an input parameter inside the input parameters dialog."""
+ expand = False
+
+ def __init__(self, param, changed_callback=None, editing_callback=None):
+ Gtk.HBox.__init__(self)
+
+ self.param = param
+ self._changed_callback = changed_callback
+ self._editing_callback = editing_callback
+
+ self.label = Gtk.Label()
+ self.label.set_size_request(150, -1)
+ self.label.show()
+ self.pack_start(self.label, False, False, 0)
+
+ self.tp = None
+ self._have_pending_changes = False
+
+ self.connect('show', self._update_gui)
+
+ def set_color(self, color):
+ pass
+
+ def set_tooltip_text(self, text):
+ pass
+
+ def get_text(self):
+ raise NotImplementedError()
+
+ def _update_gui(self, *args):
+ """
+ Set the markup, color, tooltip, show/hide.
+ """
+ self.label.set_markup(self.param.format_label_markup(self._have_pending_changes))
+
+ # fixme: find a non-deprecated way to change colors
+ # self.set_color(Colors.PARAM_ENTRY_COLORS.get(
+ # self.param.get_type(), Colors.PARAM_ENTRY_DEFAULT_COLOR)
+ # )
+
+ self.set_tooltip_text(self.param.format_tooltip_text())
+
+ if self.param.get_hide() == 'all':
+ self.hide()
+ else:
+ self.show_all()
+
+ def _mark_changed(self, *args):
+ """
+ Mark this param as modified on change, but validate only on focus-lost
+ """
+ self._have_pending_changes = True
+ self._update_gui()
+ if self._editing_callback:
+ self._editing_callback(self, None)
+
+ def _apply_change(self, *args):
+ """
+ Handle a gui change by setting the new param value,
+ calling the callback (if applicable), and updating.
+ """
+ #set the new value
+ self.param.set_value(self.get_text())
+ #call the callback
+ if self._changed_callback:
+ self._changed_callback(self, None)
+ else:
+ self.param.validate()
+ #gui update
+ self._have_pending_changes = False
+ self._update_gui()
+
+ def _handle_key_press(self, widget, event):
+ if event.keyval == Gdk.KEY_Return and event.get_state() & Gdk.ModifierType.CONTROL_MASK:
+ self._apply_change(widget, event)
+ return True
+ return False
+
+ def apply_pending_changes(self):
+ if self._have_pending_changes:
+ self._apply_change()
+
+
+class EntryParam(InputParam):
+ """Provide an entry box for strings and numbers."""
+
+ def __init__(self, *args, **kwargs):
+ InputParam.__init__(self, *args, **kwargs)
+ self._input = Gtk.Entry()
+ self._input.set_text(self.param.get_value())
+ self._input.connect('changed', self._mark_changed)
+ self._input.connect('focus-out-event', self._apply_change)
+ self._input.connect('key-press-event', self._handle_key_press)
+ self.pack_start(self._input, True, True, 0)
+
+ def get_text(self):
+ return self._input.get_text()
+
+ def set_color(self, color):
+ self._input.override_background_color(Gtk.StateType.NORMAL, color)
+
+ def set_tooltip_text(self, text):
+ self._input.set_tooltip_text(text)
+
+
+class MultiLineEntryParam(InputParam):
+ """Provide an multi-line box for strings."""
+ expand = True
+
+ def __init__(self, *args, **kwargs):
+ InputParam.__init__(self, *args, **kwargs)
+ self._buffer = Gtk.TextBuffer()
+ self._buffer.set_text(self.param.get_value())
+ self._buffer.connect('changed', self._mark_changed)
+
+ self._view = Gtk.TextView()
+ self._view.set_buffer(self._buffer)
+ self._view.connect('focus-out-event', self._apply_change)
+ self._view.connect('key-press-event', self._handle_key_press)
+ # fixme: add border to TextView
+
+ self._sw = Gtk.ScrolledWindow()
+ self._sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
+ self._sw.add_with_viewport(self._view)
+
+ self.pack_start(self._sw, True, True, True)
+
+ def get_text(self):
+ buf = self._buffer
+ text = buf.get_text(buf.get_start_iter(), buf.get_end_iter(),
+ include_hidden_chars=False)
+ return text.strip()
+
+ def set_color(self, color):
+ self._view.override_background_color(Gtk.StateType.NORMAL, color)
+
+ def set_tooltip_text(self, text):
+ self._view.set_tooltip_text(text)
+
+
+class PythonEditorParam(InputParam):
+
+ def __init__(self, *args, **kwargs):
+ InputParam.__init__(self, *args, **kwargs)
+ button = self._button = Gtk.Button('Open in Editor')
+ button.connect('clicked', self.open_editor)
+ self.pack_start(button, True)
+
+ def open_editor(self, widget=None):
+ self.param.parent_flowgraph.install_external_editor(self.param)
+
+ def get_text(self):
+ pass # we never update the value from here
+
+ def _apply_change(self, *args):
+ pass
+
+
+class EnumParam(InputParam):
+ """Provide an entry box for Enum types with a drop down menu."""
+
+ def __init__(self, *args, **kwargs):
+ InputParam.__init__(self, *args, **kwargs)
+ self._input = Gtk.ComboBoxText()
+ for option in self.param.get_options():
+ self._input.append_text(option.get_name())
+
+ value = self.param.get_value()
+ active_index = self.param.get_option_keys().index(value)
+ self._input.set_active(active_index)
+
+ self._input.connect('changed', self._editing_callback)
+ self._input.connect('changed', self._apply_change)
+ self.pack_start(self._input, False, False, 0)
+
+ def get_text(self):
+ return self.param.get_option_keys()[self._input.get_active()]
+
+ def set_tooltip_text(self, text):
+ self._input.set_tooltip_text(text)
+
+
+class EnumEntryParam(InputParam):
+ """Provide an entry box and drop down menu for Raw Enum types."""
+
+ def __init__(self, *args, **kwargs):
+ InputParam.__init__(self, *args, **kwargs)
+ self._input = Gtk.ComboBoxText.new_with_entry()
+ for option in self.param.get_options():
+ self._input.append_text(option.get_name())
+
+ value = self.param.get_value()
+ try:
+ active_index = self.param.get_option_keys().index(value)
+ self._input.set_active(active_index)
+ except ValueError:
+ self._input.set_active(-1)
+ self._input.get_child().set_text(value)
+
+ self._input.connect('changed', self._apply_change)
+ self._input.get_child().connect('changed', self._mark_changed)
+ self._input.get_child().connect('focus-out-event', self._apply_change)
+ self._input.get_child().connect('key-press-event', self._handle_key_press)
+ self.pack_start(self._input, False, False, 0)
+
+ @property
+ def has_custom_value(self):
+ return self._input.get_active() == -1
+
+ def get_text(self):
+ if self.has_custom_value:
+ return self._input.get_child().get_text()
+ else:
+ return self.param.get_option_keys()[self._input.get_active()]
+
+ def set_tooltip_text(self, text):
+ if self.has_custom_value: # custom entry
+ self._input.get_child().set_tooltip_text(text)
+ else:
+ self._input.set_tooltip_text(text)
+
+ def set_color(self, color):
+ self._input.get_child().modify_base(
+ Gtk.StateType.NORMAL,
+ color if not self.has_custom_value else Colors.PARAM_ENTRY_ENUM_CUSTOM_COLOR
+ )
+
+
+class FileParam(EntryParam):
+ """Provide an entry box for filename and a button to browse for a file."""
+
+ def __init__(self, *args, **kwargs):
+ EntryParam.__init__(self, *args, **kwargs)
+ self._open_button = Gtk.Button(label='...')
+ self._open_button.connect('clicked', self._handle_clicked)
+ self.pack_start(self._open_button, False, False, 0)
+
+ def _handle_clicked(self, widget=None):
+ """
+ If the button was clicked, open a file dialog in open/save format.
+ Replace the text in the entry with the new filename from the file dialog.
+ """
+ #get the paths
+ file_path = self.param.is_valid() and self.param.get_evaluated() or ''
+ (dirname, basename) = os.path.isfile(file_path) and os.path.split(file_path) or (file_path, '')
+ # check for qss theme default directory
+ if self.param.get_key() == 'qt_qss_theme':
+ dirname = os.path.dirname(dirname) # trim filename
+ if not os.path.exists(dirname):
+ config = self.param.parent_platform.config
+ dirname = os.path.join(config.install_prefix, '/share/gnuradio/themes')
+ if not os.path.exists(dirname):
+ dirname = os.getcwd() # fix bad paths
+
+ #build the dialog
+ if self.param.get_type() == 'file_open':
+ file_dialog = Gtk.FileChooserDialog('Open a Data File...', None,
+ Gtk.FileChooserAction.OPEN, ('gtk-cancel',Gtk.ResponseType.CANCEL,'gtk-open',Gtk.ResponseType.OK))
+ elif self.param.get_type() == 'file_save':
+ file_dialog = Gtk.FileChooserDialog('Save a Data File...', None,
+ Gtk.FileChooserAction.SAVE, ('gtk-cancel',Gtk.ResponseType.CANCEL, 'gtk-save',Gtk.ResponseType.OK))
+ file_dialog.set_do_overwrite_confirmation(True)
+ file_dialog.set_current_name(basename) #show the current filename
+ else:
+ raise ValueError("Can't open file chooser dialog for type " + repr(self.param.get_type()))
+ file_dialog.set_current_folder(dirname) #current directory
+ file_dialog.set_select_multiple(False)
+ file_dialog.set_local_only(True)
+ if Gtk.ResponseType.OK == file_dialog.run(): #run the dialog
+ file_path = file_dialog.get_filename() #get the file path
+ self._input.set_text(file_path)
+ self._editing_callback()
+ self._apply_change()
+ file_dialog.destroy() # destroy the dialog
diff --git a/grc/gui/ParserErrorsDialog.py b/grc/gui/ParserErrorsDialog.py
index 68ee459414..28cc8ece0c 100644
--- a/grc/gui/ParserErrorsDialog.py
+++ b/grc/gui/ParserErrorsDialog.py
@@ -17,14 +17,19 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import pygtk
-pygtk.require('2.0')
-import gtk
+from __future__ import absolute_import
-from Constants import MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT
+import six
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+from gi.repository import GObject
-class ParserErrorsDialog(gtk.Dialog):
+from .Constants import MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT
+
+
+class ParserErrorsDialog(Gtk.Dialog):
"""
A dialog for viewing parser errors
"""
@@ -36,31 +41,31 @@ class ParserErrorsDialog(gtk.Dialog):
Args:
block: a block instance
"""
- gtk.Dialog.__init__(self, title='Parser Errors', buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT))
+ GObject.GObject.__init__(self, title='Parser Errors', buttons=(Gtk.STOCK_CLOSE, Gtk.ResponseType.ACCEPT))
self._error_logs = None
- self.tree_store = gtk.TreeStore(str)
+ self.tree_store = Gtk.TreeStore(str)
self.update_tree_store(error_logs)
- column = gtk.TreeViewColumn('XML Parser Errors by Filename')
- renderer = gtk.CellRendererText()
+ column = Gtk.TreeViewColumn('XML Parser Errors by Filename')
+ renderer = Gtk.CellRendererText()
column.pack_start(renderer, True)
column.add_attribute(renderer, 'text', 0)
column.set_sort_column_id(0)
- self.tree_view = tree_view = gtk.TreeView(self.tree_store)
+ self.tree_view = tree_view = Gtk.TreeView(self.tree_store)
tree_view.set_enable_search(False) # disable pop up search box
tree_view.set_search_column(-1) # really disable search
tree_view.set_reorderable(False)
tree_view.set_headers_visible(False)
- tree_view.get_selection().set_mode(gtk.SELECTION_NONE)
+ tree_view.get_selection().set_mode(Gtk.SelectionMode.NONE)
tree_view.append_column(column)
for row in self.tree_store:
tree_view.expand_row(row.path, False)
- scrolled_window = gtk.ScrolledWindow()
- scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled_window = Gtk.ScrolledWindow()
+ scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scrolled_window.add_with_viewport(tree_view)
self.vbox.pack_start(scrolled_window, True)
@@ -71,7 +76,7 @@ class ParserErrorsDialog(gtk.Dialog):
"""set up data model"""
self.tree_store.clear()
self._error_logs = error_logs
- for filename, errors in error_logs.iteritems():
+ for filename, errors in six.iteritems(error_logs):
parent = self.tree_store.append(None, [str(filename)])
try:
with open(filename, 'r') as fp:
@@ -95,6 +100,6 @@ class ParserErrorsDialog(gtk.Dialog):
Returns:
true if the response was accept
"""
- response = gtk.Dialog.run(self)
+ response = Gtk.Dialog.run(self)
self.destroy()
- return response == gtk.RESPONSE_ACCEPT
+ return response == Gtk.ResponseType.ACCEPT
diff --git a/grc/gui/Platform.py b/grc/gui/Platform.py
index 500df1cce4..997e96ab59 100644
--- a/grc/gui/Platform.py
+++ b/grc/gui/Platform.py
@@ -17,6 +17,8 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+from __future__ import absolute_import, print_function
+
import os
import sys
@@ -58,7 +60,7 @@ class Platform(Element, _Platform):
import shutil
shutil.move(old_gui_prefs_file, gui_prefs_file)
except Exception as e:
- print >> sys.stderr, e
+ print(e, file=sys.stderr)
##############################################
# Constructors
diff --git a/grc/gui/Port.py b/grc/gui/Port.py
index 6314b7ede8..8c4500f960 100644
--- a/grc/gui/Port.py
+++ b/grc/gui/Port.py
@@ -17,23 +17,23 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import pygtk
-pygtk.require('2.0')
-import gtk
+from __future__ import absolute_import
+import math
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk, PangoCairo
from . import Actions, Colors, Utils
from .Constants import (
PORT_SEPARATION, PORT_SPACING, CONNECTOR_EXTENSION_MINIMAL,
- CONNECTOR_EXTENSION_INCREMENT, PORT_LABEL_PADDING, PORT_MIN_WIDTH, PORT_LABEL_HIDDEN_WIDTH, PORT_FONT
+ CONNECTOR_EXTENSION_INCREMENT, PORT_LABEL_PADDING, PORT_MIN_WIDTH,
+ PORT_LABEL_HIDDEN_WIDTH, PORT_FONT
)
from .Element import Element
from ..core.Constants import DEFAULT_DOMAIN, GR_MESSAGE_DOMAIN
from ..core.Port import Port as _Port
-PORT_MARKUP_TMPL="""\
-<span foreground="black" font_desc="$font">$encode($port.get_name())</span>"""
-
class Port(_Port, Element):
"""The graphical port."""
@@ -45,15 +45,19 @@ class Port(_Port, Element):
"""
_Port.__init__(self, block, n, dir)
Element.__init__(self)
- self.W = self.H = self.w = self.h = 0
+ self.W = self.w = self.h = 0
+ self.H = 20 # todo: fix
self._connector_coordinate = (0, 0)
self._connector_length = 0
self._hovering = True
self._force_label_unhidden = False
+ self.layout = Gtk.DrawingArea().create_pango_layout('')
+ self._bg_color = Colors.get_color(self.get_color())
def create_shapes(self):
"""Create new areas and labels for the port."""
Element.create_shapes(self)
+ self._bg_color = Colors.get_color(self.get_color())
if self.get_hide():
return # this port is hidden, no need to create shapes
if self.get_domain() == GR_MESSAGE_DOMAIN:
@@ -63,9 +67,9 @@ class Port(_Port, Element):
#get current rotation
rotation = self.get_rotation()
#get all sibling ports
- ports = self.get_parent().get_sources_gui() \
- if self.is_source else self.get_parent().get_sinks_gui()
- ports = filter(lambda p: not p.get_hide(), ports)
+ ports = self.parent.get_sources_gui() \
+ if self.is_source else self.parent.get_sinks_gui()
+ ports = [p for p in ports if not p.get_hide()]
#get the max width
self.W = max([port.W for port in ports] + [PORT_MIN_WIDTH])
W = self.W if not self._label_hidden() else PORT_LABEL_HIDDEN_WIDTH
@@ -76,16 +80,15 @@ class Port(_Port, Element):
if hasattr(self, '_connector_length'):
del self._connector_length
return
- length = len(filter(lambda p: not p.get_hide(), ports))
#reverse the order of ports for these rotations
if rotation in (180, 270):
- index = length-index-1
+ index = len(ports)-index-1
port_separation = PORT_SEPARATION \
- if not self.get_parent().has_busses[self.is_source] \
+ if not self.parent.has_busses[self.is_source] \
else max([port.H for port in ports]) + PORT_SPACING
- offset = (self.get_parent().H - (length-1)*port_separation - self.H)/2
+ offset = (self.parent.H - (len(ports)-1)*port_separation - self.H)/2
#create areas and connector coordinates
if (self.is_sink and rotation == 0) or (self.is_source and rotation == 180):
x = -W
@@ -93,7 +96,7 @@ class Port(_Port, Element):
self.add_area((x, y), (W, self.H))
self._connector_coordinate = (x-1, y+self.H/2)
elif (self.is_source and rotation == 0) or (self.is_sink and rotation == 180):
- x = self.get_parent().W
+ x = self.parent.W
y = port_separation*index+offset
self.add_area((x, y), (W, self.H))
self._connector_coordinate = (x+1+W, y+self.H/2)
@@ -103,7 +106,7 @@ class Port(_Port, Element):
self.add_area((x, y), (self.H, W))
self._connector_coordinate = (x+self.H/2, y-1)
elif (self.is_sink and rotation == 90) or (self.is_source and rotation == 270):
- y = self.get_parent().W
+ y = self.parent.W
x = port_separation*index+offset
self.add_area((x, y), (self.H, W))
self._connector_coordinate = (x+self.H/2, y+1+W)
@@ -112,50 +115,36 @@ class Port(_Port, Element):
def create_labels(self):
"""Create the labels for the socket."""
- Element.create_labels(self)
- self._bg_color = Colors.get_color(self.get_color())
- # create the layout
- layout = gtk.DrawingArea().create_pango_layout('')
- layout.set_markup(Utils.parse_template(PORT_MARKUP_TMPL, port=self, font=PORT_FONT))
- self.w, self.h = layout.get_pixel_size()
- self.W = 2 * PORT_LABEL_PADDING + self.w
- self.H = 2 * PORT_LABEL_PADDING + self.h * (
- 3 if self.get_type() == 'bus' else 1)
- self.H += self.H % 2
- # create the pixmap
- pixmap = self.get_parent().get_parent().new_pixmap(self.w, self.h)
- gc = pixmap.new_gc()
- gc.set_foreground(self._bg_color)
- pixmap.draw_rectangle(gc, True, 0, 0, self.w, self.h)
- pixmap.draw_layout(gc, 0, 0, layout)
- # create vertical and horizontal pixmaps
- self.horizontal_label = pixmap
- if self.is_vertical():
- self.vertical_label = self.get_parent().get_parent().new_pixmap(self.h, self.w)
- Utils.rotate_pixmap(gc, self.horizontal_label, self.vertical_label)
-
- def draw(self, gc, window):
+ self.layout.set_markup("""<span foreground="black" font_desc="{font}">{name}</span>""".format(
+ name=Utils.encode(self.get_name()), font=PORT_FONT
+ ))
+
+ def draw(self, widget, cr):
"""
Draw the socket with a label.
-
- Args:
- gc: the graphics context
- window: the gtk window to draw on
"""
- Element.draw(
- self, gc, window, bg_color=self._bg_color,
- border_color=self.is_highlighted() and Colors.HIGHLIGHT_COLOR or
- self.get_parent().is_dummy_block and Colors.MISSING_BLOCK_BORDER_COLOR or
- Colors.BORDER_COLOR,
+ border_color = (
+ Colors.HIGHLIGHT_COLOR if self.is_highlighted() else
+ Colors.MISSING_BLOCK_BORDER_COLOR if self.parent.is_dummy_block else
+ Colors.BORDER_COLOR
)
+ Element.draw(self, widget, cr, border_color, self._bg_color)
+
if not self._areas_list or self._label_hidden():
return # this port is either hidden (no areas) or folded (no label)
X, Y = self.get_coordinate()
- (x, y), (w, h) = self._areas_list[0] # use the first area's sizes to place the labels
+ (x, y), _ = self._areas_list[0]
+ cr.set_source_rgb(*self._bg_color)
+ cr.save()
if self.is_horizontal():
- window.draw_drawable(gc, self.horizontal_label, 0, 0, x+X+(self.W-self.w)/2, y+Y+(self.H-self.h)/2, -1, -1)
+ cr.translate(x + X + (self.W - self.w) / 2, y + Y + (self.H - self.h) / 2)
elif self.is_vertical():
- window.draw_drawable(gc, self.vertical_label, 0, 0, x+X+(self.H-self.h)/2, y+Y+(self.W-self.w)/2, -1, -1)
+ cr.translate(x + X + (self.H - self.h) / 2, y + Y + (self.W - self.w) / 2)
+ cr.rotate(-90 * math.pi / 180.)
+ cr.translate(-self.w, 0)
+ PangoCairo.update_layout(cr, self.layout)
+ PangoCairo.show_layout(cr, self.layout)
+ cr.restore()
def get_connector_coordinate(self):
"""
@@ -197,7 +186,7 @@ class Port(_Port, Element):
Returns:
the parent's rotation
"""
- return self.get_parent().get_rotation()
+ return self.parent.get_rotation()
def move(self, delta_coor):
"""
@@ -206,7 +195,7 @@ class Port(_Port, Element):
Args:
delta_corr: the (delta_x, delta_y) tuple
"""
- self.get_parent().move(delta_coor)
+ self.parent.move(delta_coor)
def rotate(self, direction):
"""
@@ -215,7 +204,7 @@ class Port(_Port, Element):
Args:
direction: degrees to rotate
"""
- self.get_parent().rotate(direction)
+ self.parent.rotate(direction)
def get_coordinate(self):
"""
@@ -224,7 +213,7 @@ class Port(_Port, Element):
Returns:
the parents coordinate
"""
- return self.get_parent().get_coordinate()
+ return self.parent.get_coordinate()
def set_highlighted(self, highlight):
"""
@@ -233,7 +222,7 @@ class Port(_Port, Element):
Args:
highlight: true to enable highlighting
"""
- self.get_parent().set_highlighted(highlight)
+ self.parent.set_highlighted(highlight)
def is_highlighted(self):
"""
@@ -242,7 +231,7 @@ class Port(_Port, Element):
Returns:
the parent's highlighting status
"""
- return self.get_parent().is_highlighted()
+ return self.parent.is_highlighted()
def _label_hidden(self):
"""
diff --git a/grc/gui/Preferences.py b/grc/gui/Preferences.py
index 5fbdfe927a..8756a7ab23 100644
--- a/grc/gui/Preferences.py
+++ b/grc/gui/Preferences.py
@@ -17,9 +17,12 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+from __future__ import absolute_import, print_function
+
import os
import sys
-import ConfigParser
+
+from six.moves import configparser
HEADER = """\
@@ -31,7 +34,7 @@ HEADER = """\
"""
_platform = None
-_config_parser = ConfigParser.SafeConfigParser()
+_config_parser = configparser.SafeConfigParser()
def file_extension():
@@ -45,12 +48,12 @@ def load(platform):
for section in ['main', 'files_open', 'files_recent']:
try:
_config_parser.add_section(section)
- except Exception, e:
- print e
+ except Exception as e:
+ print(e)
try:
_config_parser.read(_platform.get_prefs_file())
except Exception as err:
- print >> sys.stderr, err
+ print(err, file=sys.stderr)
def save():
@@ -59,7 +62,7 @@ def save():
fp.write(HEADER)
_config_parser.write(fp)
except Exception as err:
- print >> sys.stderr, err
+ print(err, file=sys.stderr)
def entry(key, value=None, default=None):
@@ -74,7 +77,7 @@ def entry(key, value=None, default=None):
}.get(_type, _config_parser.get)
try:
result = getter('main', key)
- except ConfigParser.Error:
+ except configparser.Error:
result = _type() if default is None else default
return result
@@ -106,7 +109,7 @@ def get_file_list(key):
try:
files = [value for name, value in _config_parser.items(key)
if name.startswith('%s_' % key)]
- except ConfigParser.Error:
+ except configparser.Error:
files = []
return files
@@ -121,7 +124,7 @@ def set_open_files(files):
def get_recent_files():
""" Gets recent files, removes any that do not exist and re-saves it """
- files = filter(os.path.exists, get_file_list('files_recent'))
+ files = list(filter(os.path.exists, get_file_list('files_recent')))
set_recent_files(files)
return files
diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py
index 7c66a77a54..f87ca6e7c1 100644
--- a/grc/gui/PropsDialog.py
+++ b/grc/gui/PropsDialog.py
@@ -17,114 +17,95 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import pygtk
-pygtk.require('2.0')
-import gtk
+from __future__ import absolute_import
+from gi.repository import Gtk, Gdk, GObject, Pango
-import Actions
-from Dialogs import SimpleTextDisplay
-from Constants import MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT, FONT_SIZE
-import Utils
-import pango
+from . import Actions, Utils, Constants
+from .Dialogs import SimpleTextDisplay
+import six
-TAB_LABEL_MARKUP_TMPL="""\
-#set $foreground = $valid and 'black' or 'red'
-<span foreground="$foreground">$encode($tab)</span>"""
-
-def get_title_label(title):
- """
- Get a title label for the params window.
- The title will be bold, underlined, and left justified.
-
- Args:
- title: the text of the title
-
- Returns:
- a gtk object
- """
- label = gtk.Label()
- label.set_markup('\n<b><span underline="low">%s</span>:</b>\n'%title)
- hbox = gtk.HBox()
- hbox.pack_start(label, False, False, padding=11)
- return hbox
-
-
-class PropsDialog(gtk.Dialog):
+class PropsDialog(Gtk.Dialog):
"""
A dialog to set block parameters, view errors, and view documentation.
"""
- def __init__(self, block):
+ def __init__(self, parent, block):
"""
Properties dialog constructor.
- Args:
+ Args:%
block: a block instance
"""
- self._hash = 0
- gtk.Dialog.__init__(
+ Gtk.Dialog.__init__(
self,
- title='Properties: %s' % block.get_name(),
- buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
- gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
- gtk.STOCK_APPLY, gtk.RESPONSE_APPLY)
+ title='Properties: ' + block.get_name(),
+ transient_for=parent,
+ modal=True,
+ )
+ self.add_buttons(
+ Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT,
+ Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT,
+ Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY,
)
- self.set_response_sensitive(gtk.RESPONSE_APPLY, False)
- self.set_size_request(MIN_DIALOG_WIDTH, MIN_DIALOG_HEIGHT)
+ self.set_response_sensitive(Gtk.ResponseType.APPLY, False)
+ self.set_size_request(Constants.MIN_DIALOG_WIDTH, Constants.MIN_DIALOG_HEIGHT)
+
self._block = block
+ self._hash = 0
- vpaned = gtk.VPaned()
- self.vbox.pack_start(vpaned)
+ vpaned = Gtk.VPaned()
+ self.vbox.pack_start(vpaned, True, True, 0)
# Notebook to hold param boxes
- notebook = gtk.Notebook()
+ notebook = Gtk.Notebook()
notebook.set_show_border(False)
notebook.set_scrollable(True) # scroll arrows for page tabs
- notebook.set_tab_pos(gtk.POS_TOP)
+ notebook.set_tab_pos(Gtk.PositionType.TOP)
vpaned.pack1(notebook, True)
# Params boxes for block parameters
self._params_boxes = list()
for tab in block.get_param_tab_labels():
- label = gtk.Label()
- vbox = gtk.VBox()
- scroll_box = gtk.ScrolledWindow()
- scroll_box.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ label = Gtk.Label()
+ vbox = Gtk.VBox()
+ scroll_box = Gtk.ScrolledWindow()
+ scroll_box.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scroll_box.add_with_viewport(vbox)
notebook.append_page(scroll_box, label)
self._params_boxes.append((tab, label, vbox))
# Docs for the block
self._docs_text_display = doc_view = SimpleTextDisplay()
- doc_view.get_buffer().create_tag('b', weight=pango.WEIGHT_BOLD)
- self._docs_box = gtk.ScrolledWindow()
- self._docs_box.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ doc_view.get_buffer().create_tag('b', weight=Pango.Weight.BOLD)
+ self._docs_box = Gtk.ScrolledWindow()
+ self._docs_box.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self._docs_box.add_with_viewport(self._docs_text_display)
- notebook.append_page(self._docs_box, gtk.Label("Documentation"))
+ notebook.append_page(self._docs_box, Gtk.Label(label="Documentation"))
# Generated code for the block
if Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB.get_active():
self._code_text_display = code_view = SimpleTextDisplay()
- code_view.set_wrap_mode(gtk.WRAP_NONE)
- code_view.get_buffer().create_tag('b', weight=pango.WEIGHT_BOLD)
- code_view.modify_font(pango.FontDescription(
- 'monospace %d' % FONT_SIZE))
- code_box = gtk.ScrolledWindow()
- code_box.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ code_view.set_wrap_mode(Gtk.WrapMode.NONE)
+ code_view.get_buffer().create_tag('b', weight=Pango.Weight.BOLD)
+ code_view.set_monospace(True)
+ # todo: set font size in non-deprecated way
+ # code_view.override_font(Pango.FontDescription('monospace %d' % Constants.FONT_SIZE))
+ code_box = Gtk.ScrolledWindow()
+ code_box.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
code_box.add_with_viewport(self._code_text_display)
- notebook.append_page(code_box, gtk.Label("Generated Code"))
+ notebook.append_page(code_box, Gtk.Label(label="Generated Code"))
else:
self._code_text_display = None
# Error Messages for the block
self._error_messages_text_display = SimpleTextDisplay()
- self._error_box = gtk.ScrolledWindow()
- self._error_box.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self._error_box = Gtk.ScrolledWindow()
+ self._error_box.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self._error_box.add_with_viewport(self._error_messages_text_display)
vpaned.pack2(self._error_box)
- vpaned.set_position(int(0.65 * MIN_DIALOG_HEIGHT))
+ vpaned.set_position(int(0.65 * Constants.MIN_DIALOG_HEIGHT))
# Connect events
self.connect('key-press-event', self._handle_key_press)
@@ -144,25 +125,23 @@ class PropsDialog(gtk.Dialog):
true if changed
"""
old_hash = self._hash
- # create a tuple of things from each param that affects the params box
- self._hash = hash(tuple([(
- hash(param), param.get_name(), param.get_type(),
- param.get_hide() == 'all',
- ) for param in self._block.get_params()]))
- return self._hash != old_hash
+ new_hash = self._hash = hash(tuple(
+ (hash(param), param.get_name(), param.get_type(), param.get_hide() == 'all',)
+ for param in self._block.get_params()
+ ))
+ return new_hash != old_hash
def _handle_changed(self, *args):
"""
A change occurred within a param:
Rewrite/validate the block and update the gui.
"""
- # update for the block
self._block.rewrite()
self._block.validate()
self.update_gui()
def _activate_apply(self, *args):
- self.set_response_sensitive(gtk.RESPONSE_APPLY, True)
+ self.set_response_sensitive(Gtk.ResponseType.APPLY, True)
def update_gui(self, widget=None, force=False):
"""
@@ -173,43 +152,46 @@ class PropsDialog(gtk.Dialog):
Update the documentation block.
Hide the box if there are no docs.
"""
- # update the params box
if force or self._params_changed():
# hide params box before changing
for tab, label, vbox in self._params_boxes:
- vbox.hide_all()
+ vbox.hide()
# empty the params box
for child in vbox.get_children():
vbox.remove(child)
- child.destroy()
+ # child.destroy() # disabled because it throw errors...
# repopulate the params box
box_all_valid = True
- for param in filter(lambda p: p.get_tab_label() == tab, self._block.get_params()):
+ for param in [p for p in self._block.get_params() if p.get_tab_label() == tab]:
+ # fixme: why do we even rebuild instead of really hiding params?
if param.get_hide() == 'all':
continue
box_all_valid = box_all_valid and param.is_valid()
+
input_widget = param.get_input(self._handle_changed, self._activate_apply)
- vbox.pack_start(input_widget, input_widget.expand)
- label.set_markup(Utils.parse_template(TAB_LABEL_MARKUP_TMPL, valid=box_all_valid, tab=tab))
- # show params box with new params
- vbox.show_all()
- # update the errors box
+ input_widget.show_all()
+ vbox.pack_start(input_widget, input_widget.expand, True, 1)
+
+ label.set_markup('<span foreground="{color}">{name}</span>'.format(
+ color='black' if box_all_valid else 'red', name=Utils.encode(tab)
+ ))
+ vbox.show() # show params box with new params
+
if self._block.is_valid():
self._error_box.hide()
else:
self._error_box.show()
messages = '\n\n'.join(self._block.get_error_messages())
self._error_messages_text_display.set_text(messages)
- # update the docs box
+
self._update_docs_page()
- # update the generated code
self._update_generated_code_page()
def _update_docs_page(self):
"""Show documentation from XML and try to display best matching docstring"""
- buffer = self._docs_text_display.get_buffer()
- buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
- pos = buffer.get_end_iter()
+ buf = self._docs_text_display.get_buffer()
+ buf.delete(buf.get_start_iter(), buf.get_end_iter())
+ pos = buf.get_end_iter()
docstrings = self._block.get_doc()
if not docstrings:
@@ -219,11 +201,11 @@ class PropsDialog(gtk.Dialog):
from_xml = docstrings.pop('', '')
for line in from_xml.splitlines():
if line.lstrip() == line and line.endswith(':'):
- buffer.insert_with_tags_by_name(pos, line + '\n', 'b')
+ buf.insert_with_tags_by_name(pos, line + '\n', 'b')
else:
- buffer.insert(pos, line + '\n')
+ buf.insert(pos, line + '\n')
if from_xml:
- buffer.insert(pos, '\n')
+ buf.insert(pos, '\n')
# if given the current parameters an exact match can be made
block_constructor = self._block.get_make().rsplit('.', 2)[-1]
@@ -232,17 +214,17 @@ class PropsDialog(gtk.Dialog):
docstrings = {block_class: docstrings[block_class]}
# show docstring(s) extracted from python sources
- for cls_name, docstring in docstrings.iteritems():
- buffer.insert_with_tags_by_name(pos, cls_name + '\n', 'b')
- buffer.insert(pos, docstring + '\n\n')
+ for cls_name, docstring in six.iteritems(docstrings):
+ buf.insert_with_tags_by_name(pos, cls_name + '\n', 'b')
+ buf.insert(pos, docstring + '\n\n')
pos.backward_chars(2)
- buffer.delete(pos, buffer.get_end_iter())
+ buf.delete(pos, buf.get_end_iter())
def _update_generated_code_page(self):
if not self._code_text_display:
return # user disabled code preview
- buffer = self._code_text_display.get_buffer()
+ buf = self._code_text_display.get_buffer()
block = self._block
key = block.get_key()
@@ -256,40 +238,34 @@ class PropsDialog(gtk.Dialog):
def insert(header, text):
if not text:
return
- buffer.insert_with_tags_by_name(buffer.get_end_iter(), header, 'b')
- buffer.insert(buffer.get_end_iter(), text)
+ buf.insert_with_tags_by_name(buf.get_end_iter(), header, 'b')
+ buf.insert(buf.get_end_iter(), text)
- buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
+ buf.delete(buf.get_start_iter(), buf.get_end_iter())
insert('# Imports\n', '\n'.join(block.get_imports()))
- if key.startswith('variable'):
+ if block.is_variable:
insert('\n\n# Variables\n', block.get_var_make())
insert('\n\n# Blocks\n', block.get_make())
if src:
insert('\n\n# External Code ({}.py)\n'.format(block.get_id()), src)
def _handle_key_press(self, widget, event):
- """
- Handle key presses from the keyboard.
- Call the ok response when enter is pressed.
-
- Returns:
- false to forward the keypress
- """
- if (event.keyval == gtk.keysyms.Return and
- event.state & gtk.gdk.CONTROL_MASK == 0 and
- not isinstance(widget.get_focus(), gtk.TextView)
- ):
- self.response(gtk.RESPONSE_ACCEPT)
+ close_dialog = (
+ event.keyval == Gdk.KEY_Return and
+ event.get_state() & Gdk.ModifierType.CONTROL_MASK == 0 and
+ not isinstance(widget.get_focus(), Gtk.TextView)
+ )
+ if close_dialog:
+ self.response(Gtk.ResponseType.ACCEPT)
return True # handled here
+
return False # forward the keypress
def _handle_response(self, widget, response):
- if response in (gtk.RESPONSE_APPLY, gtk.RESPONSE_ACCEPT):
+ if response in (Gtk.ResponseType.APPLY, Gtk.ResponseType.ACCEPT):
for tab, label, vbox in self._params_boxes:
for child in vbox.get_children():
child.apply_pending_changes()
- self.set_response_sensitive(gtk.RESPONSE_APPLY, False)
+ self.set_response_sensitive(Gtk.ResponseType.APPLY, False)
return True
return False
-
-
diff --git a/grc/gui/StateCache.py b/grc/gui/StateCache.py
index 3cdb5f30ce..b109a1281b 100644
--- a/grc/gui/StateCache.py
+++ b/grc/gui/StateCache.py
@@ -17,8 +17,9 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import Actions
-from Constants import STATE_CACHE_SIZE
+from __future__ import absolute_import
+from . import Actions
+from .Constants import STATE_CACHE_SIZE
class StateCache(object):
"""
diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py
index 51b9b19e9f..e5d4ccaa35 100644
--- a/grc/gui/Utils.py
+++ b/grc/gui/Utils.py
@@ -17,37 +17,13 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-import pygtk
-pygtk.require('2.0')
-import gtk
-import gobject
+from __future__ import absolute_import
-from Cheetah.Template import Template
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import GLib
-from Constants import POSSIBLE_ROTATIONS, CANVAS_GRID_SIZE
-
-
-def rotate_pixmap(gc, src_pixmap, dst_pixmap, angle=gtk.gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE):
- """
- Load the destination pixmap with a rotated version of the source pixmap.
- The source pixmap will be loaded into a pixbuf, rotated, and drawn to the destination pixmap.
- The pixbuf is a client-side drawable, where a pixmap is a server-side drawable.
-
- Args:
- gc: the graphics context
- src_pixmap: the source pixmap
- dst_pixmap: the destination pixmap
- angle: the angle to rotate by
- """
- width, height = src_pixmap.get_size()
- pixbuf = gtk.gdk.Pixbuf(
- colorspace=gtk.gdk.COLORSPACE_RGB,
- has_alpha=False, bits_per_sample=8,
- width=width, height=height,
- )
- pixbuf.get_from_drawable(src_pixmap, src_pixmap.get_colormap(), 0, 0, 0, 0, -1, -1)
- pixbuf = pixbuf.rotate_simple(angle)
- dst_pixmap.draw_pixbuf(gc, pixbuf, 0, 0, 0, 0)
+from .Constants import POSSIBLE_ROTATIONS, CANVAS_GRID_SIZE
def get_rotated_coordinate(coor, rotation):
@@ -73,7 +49,7 @@ def get_rotated_coordinate(coor, rotation):
return x * cos_r + y * sin_r, -x * sin_r + y * cos_r
-def get_angle_from_coordinates((x1, y1), (x2, y2)):
+def get_angle_from_coordinates(p1, p2):
"""
Given two points, calculate the vector direction from point1 to point2, directions are multiples of 90 degrees.
@@ -84,6 +60,8 @@ def get_angle_from_coordinates((x1, y1), (x2, y2)):
Returns:
the direction in degrees
"""
+ (x1, y1) = p1
+ (x2, y2) = p2
if y1 == y2: # 0 or 180
return 0 if x2 > x1 else 180
else: # 90 or 270
@@ -96,38 +74,15 @@ def encode(value):
Older versions of glib seg fault if the last byte starts a multi-byte
character.
"""
-
valid_utf8 = value.decode('utf-8', errors='replace').encode('utf-8')
- return gobject.markup_escape_text(valid_utf8)
-
-
-class TemplateParser(object):
- def __init__(self):
- self.cache = {}
-
- def __call__(self, tmpl_str, **kwargs):
- """
- Parse the template string with the given args.
- Pass in the xml encode method for pango escape chars.
-
- Args:
- tmpl_str: the template as a string
-
- Returns:
- a string of the parsed template
- """
- kwargs['encode'] = encode
- template = self.cache.setdefault(tmpl_str, Template.compile(tmpl_str))
- return str(template(namespaces=kwargs))
-
-parse_template = TemplateParser()
+ return GLib.markup_escape_text(valid_utf8)
def align_to_grid(coor, mode=round):
def align(value):
return int(mode(value / (1.0 * CANVAS_GRID_SIZE)) * CANVAS_GRID_SIZE)
try:
- return map(align, coor)
+ return [align(c) for c in coor]
except TypeError:
x = coor
return align(coor)
diff --git a/grc/gui/VariableEditor.py b/grc/gui/VariableEditor.py
index 7721f3bda6..399e4ec475 100644
--- a/grc/gui/VariableEditor.py
+++ b/grc/gui/VariableEditor.py
@@ -1,5 +1,5 @@
"""
-Copyright 2015 Free Software Foundation, Inc.
+Copyright 2015, 2016 Free Software Foundation, Inc.
This file is part of GNU Radio
GNU Radio Companion is free software; you can redistribute it and/or
@@ -17,12 +17,13 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
-from operator import attrgetter
+from __future__ import absolute_import
-import pygtk
-pygtk.require('2.0')
-import gtk
-import gobject
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import GObject
from . import Actions
from . import Preferences
@@ -32,34 +33,35 @@ BLOCK_INDEX = 0
ID_INDEX = 1
-class VariableEditorContextMenu(gtk.Menu):
+class VariableEditorContextMenu(Gtk.Menu):
""" A simple context menu for our variable editor """
+
def __init__(self, var_edit):
- gtk.Menu.__init__(self)
+ Gtk.Menu.__init__(self)
- self.imports = gtk.MenuItem("Add _Import")
+ self.imports = Gtk.MenuItem("Add _Import")
self.imports.connect('activate', var_edit.handle_action, var_edit.ADD_IMPORT)
self.add(self.imports)
- self.variables = gtk.MenuItem("Add _Variable")
+ self.variables = Gtk.MenuItem("Add _Variable")
self.variables.connect('activate', var_edit.handle_action, var_edit.ADD_VARIABLE)
self.add(self.variables)
- self.add(gtk.SeparatorMenuItem())
+ self.add(Gtk.SeparatorMenuItem())
- self.enable = gtk.MenuItem("_Enable")
+ self.enable = Gtk.MenuItem("_Enable")
self.enable.connect('activate', var_edit.handle_action, var_edit.ENABLE_BLOCK)
- self.disable = gtk.MenuItem("_Disable")
+ self.disable = Gtk.MenuItem("_Disable")
self.disable.connect('activate', var_edit.handle_action, var_edit.DISABLE_BLOCK)
self.add(self.enable)
self.add(self.disable)
- self.add(gtk.SeparatorMenuItem())
+ self.add(Gtk.SeparatorMenuItem())
- self.delete = gtk.MenuItem("_Delete")
+ self.delete = Gtk.MenuItem("_Delete")
self.delete.connect('activate', var_edit.handle_action, var_edit.DELETE_BLOCK)
self.add(self.delete)
- self.add(gtk.SeparatorMenuItem())
+ self.add(Gtk.SeparatorMenuItem())
- self.properties = gtk.MenuItem("_Properties...")
+ self.properties = Gtk.MenuItem("_Properties...")
self.properties.connect('activate', var_edit.handle_action, var_edit.OPEN_PROPERTIES)
self.add(self.properties)
self.show_all()
@@ -71,7 +73,7 @@ class VariableEditorContextMenu(gtk.Menu):
self.disable.set_sensitive(selected and enabled)
-class VariableEditor(gtk.VBox):
+class VariableEditor(Gtk.VBox):
# Actions that are handled by the editor
ADD_IMPORT = 0
@@ -82,23 +84,28 @@ class VariableEditor(gtk.VBox):
ENABLE_BLOCK = 5
DISABLE_BLOCK = 6
- def __init__(self, platform, get_flow_graph):
- gtk.VBox.__init__(self)
- self.platform = platform
- self.get_flow_graph = get_flow_graph
+ __gsignals__ = {
+ 'create_new_block': (GObject.SIGNAL_RUN_FIRST, None, (str,)),
+ 'remove_block': (GObject.SIGNAL_RUN_FIRST, None, (str,))
+ }
+
+ def __init__(self):
+ Gtk.VBox.__init__(self)
self._block = None
self._mouse_button_pressed = False
+ self._imports = []
+ self._variables = []
# Only use the model to store the block reference and name.
# Generate everything else dynamically
- self.treestore = gtk.TreeStore(gobject.TYPE_PYOBJECT, # Block reference
- gobject.TYPE_STRING) # Category and block name
- self.treeview = gtk.TreeView(self.treestore)
+ self.treestore = Gtk.TreeStore(GObject.TYPE_PYOBJECT, # Block reference
+ GObject.TYPE_STRING) # Category and block name
+ self.treeview = Gtk.TreeView(self.treestore)
self.treeview.set_enable_search(False)
self.treeview.set_search_column(-1)
#self.treeview.set_enable_search(True)
#self.treeview.set_search_column(ID_INDEX)
- self.treeview.get_selection().set_mode('single')
+ self.treeview.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
self.treeview.set_headers_visible(True)
self.treeview.connect('button-press-event', self._handle_mouse_button_press)
self.treeview.connect('button-release-event', self._handle_mouse_button_release)
@@ -106,67 +113,63 @@ class VariableEditor(gtk.VBox):
self.treeview.connect('key-press-event', self._handle_key_button_press)
# Block Name or Category
- self.id_cell = gtk.CellRendererText()
+ self.id_cell = Gtk.CellRendererText()
self.id_cell.connect('edited', self._handle_name_edited_cb)
- id_column = gtk.TreeViewColumn("Id", self.id_cell, text=ID_INDEX)
+ id_column = Gtk.TreeViewColumn("Id", self.id_cell, text=ID_INDEX)
id_column.set_name("id")
id_column.set_resizable(True)
id_column.set_max_width(300)
id_column.set_min_width(80)
id_column.set_fixed_width(100)
- id_column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
+ id_column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
id_column.set_cell_data_func(self.id_cell, self.set_properties)
self.id_column = id_column
self.treeview.append_column(id_column)
- self.treestore.set_sort_column_id(ID_INDEX, gtk.SORT_ASCENDING)
+ self.treestore.set_sort_column_id(ID_INDEX, Gtk.SortType.ASCENDING)
# For forcing resize
self._col_width = 0
# Block Value
- self.value_cell = gtk.CellRendererText()
+ self.value_cell = Gtk.CellRendererText()
self.value_cell.connect('edited', self._handle_value_edited_cb)
- value_column = gtk.TreeViewColumn("Value", self.value_cell)
+ value_column = Gtk.TreeViewColumn("Value", self.value_cell)
value_column.set_name("value")
value_column.set_resizable(False)
value_column.set_expand(True)
value_column.set_min_width(100)
- value_column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
+ value_column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
value_column.set_cell_data_func(self.value_cell, self.set_value)
self.value_column = value_column
self.treeview.append_column(value_column)
# Block Actions (Add, Remove)
- self.action_cell = gtk.CellRendererPixbuf()
+ self.action_cell = Gtk.CellRendererPixbuf()
value_column.pack_start(self.action_cell, False)
value_column.set_cell_data_func(self.action_cell, self.set_icon)
# Make the scrolled window to hold the tree view
- scrolled_window = gtk.ScrolledWindow()
- scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled_window = Gtk.ScrolledWindow()
+ scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scrolled_window.add_with_viewport(self.treeview)
scrolled_window.set_size_request(DEFAULT_BLOCKS_WINDOW_WIDTH, -1)
- self.pack_start(scrolled_window)
+ self.pack_start(scrolled_window, True, True, 0)
# Context menus
self._context_menu = VariableEditorContextMenu(self)
self._confirm_delete = Preferences.variable_editor_confirm_delete()
# Sets cell contents
- def set_icon(self, col, cell, model, iter):
+ def set_icon(self, col, cell, model, iter, data):
block = model.get_value(iter, BLOCK_INDEX)
- if block:
- pb = self.treeview.render_icon(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU, None)
- else:
- pb = self.treeview.render_icon(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU, None)
- cell.set_property('pixbuf', pb)
+ cell.set_property('icon-name', 'window-close' if block else 'list-add')
- def set_value(self, col, cell, model, iter):
+ def set_value(self, col, cell, model, iter, data):
sp = cell.set_property
block = model.get_value(iter, BLOCK_INDEX)
# Set the default properties for this column first.
# Some set in set_properties() may be overridden (editable for advanced variable blocks)
- self.set_properties(col, cell, model, iter)
+ self.set_properties(col, cell, model, iter, data)
# Set defaults
value = None
@@ -198,7 +201,7 @@ class VariableEditor(gtk.VBox):
# Always set the text value.
sp('text', value)
- def set_properties(self, col, cell, model, iter):
+ def set_properties(self, col, cell, model, iter, data):
sp = cell.set_property
block = model.get_value(iter, BLOCK_INDEX)
# Set defaults
@@ -217,19 +220,12 @@ class VariableEditor(gtk.VBox):
if block.get_error_messages():
sp('foreground', 'red')
- def update_gui(self):
- if not self.get_flow_graph():
- return
- self._update_blocks()
+ def update_gui(self, blocks):
+ self._imports = [block for block in blocks if block.is_import]
+ self._variables = [block for block in blocks if block.is_variable]
self._rebuild()
self.treeview.expand_all()
- def _update_blocks(self):
- self._imports = filter(attrgetter('is_import'),
- self.get_flow_graph().blocks)
- self._variables = filter(attrgetter('is_variable'),
- self.get_flow_graph().blocks)
-
def _rebuild(self, *args):
self.treestore.clear()
imports = self.treestore.append(None, [None, 'Imports'])
@@ -258,23 +254,23 @@ class VariableEditor(gtk.VBox):
key presses or mouse clicks. Also triggers an update of the flow graph and editor.
"""
if key == self.ADD_IMPORT:
- self.get_flow_graph().add_new_block('import')
+ self.emit('create_new_block', 'import')
elif key == self.ADD_VARIABLE:
- self.get_flow_graph().add_new_block('variable')
+ self.emit('create_new_block', 'variable')
elif key == self.OPEN_PROPERTIES:
Actions.BLOCK_PARAM_MODIFY(self._block)
elif key == self.DELETE_BLOCK:
- self.get_flow_graph().remove_element(self._block)
+ self.emit('remove_block', self._block.get_id())
elif key == self.DELETE_CONFIRM:
if self._confirm_delete:
# Create a context menu to confirm the delete operation
- confirmation_menu = gtk.Menu()
+ confirmation_menu = Gtk.Menu()
block_id = self._block.get_param('id').get_value().replace("_", "__")
- confirm = gtk.MenuItem("Delete {}".format(block_id))
+ confirm = Gtk.MenuItem(label="Delete {}".format(block_id))
confirm.connect('activate', self.handle_action, self.DELETE_BLOCK)
confirmation_menu.add(confirm)
confirmation_menu.show_all()
- confirmation_menu.popup(None, None, None, event.button, event.time)
+ confirmation_menu.popup(None, None, None, None, event.button, event.time)
else:
self.handle_action(None, self.DELETE_BLOCK, None)
elif key == self.ENABLE_BLOCK:
@@ -302,12 +298,12 @@ class VariableEditor(gtk.VBox):
if event.button == 1 and col.get_name() == "value":
# Make sure this has a block (not the import/variable rows)
- if self._block and event.type == gtk.gdk._2BUTTON_PRESS:
+ if self._block and event.type == Gdk.EventType._2BUTTON_PRESS:
# Open the advanced dialog if it is a gui variable
if self._block.get_key() not in ("variable", "import"):
self.handle_action(None, self.OPEN_PROPERTIES, event=event)
return True
- if event.type == gtk.gdk.BUTTON_PRESS:
+ if event.type == Gdk.EventType.BUTTON_PRESS:
# User is adding/removing blocks
# Make sure this is the action cell (Add/Remove Icons)
if path[2] > col.cell_get_position(self.action_cell)[0]:
@@ -320,15 +316,15 @@ class VariableEditor(gtk.VBox):
else:
self.handle_action(None, self.DELETE_CONFIRM, event=event)
return True
- elif event.button == 3 and event.type == gtk.gdk.BUTTON_PRESS:
+ elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
if self._block:
self._context_menu.update_sensitive(True, enabled=self._block.get_enabled())
else:
self._context_menu.update_sensitive(False)
- self._context_menu.popup(None, None, None, event.button, event.time)
+ self._context_menu.popup(None, None, None, None, event.button, event.time)
# Null handler. Stops the treeview from handling double click events.
- if event.type == gtk.gdk._2BUTTON_PRESS:
+ if event.type == Gdk.EventType._2BUTTON_PRESS:
return True
return False
diff --git a/grc/gui/external_editor.py b/grc/gui/external_editor.py
index 76f21412b0..11d6fd7ebb 100644
--- a/grc/gui/external_editor.py
+++ b/grc/gui/external_editor.py
@@ -17,6 +17,8 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+from __future__ import absolute_import, print_function
+
import os
import sys
import time
@@ -71,7 +73,7 @@ class ExternalEditor(threading.Thread):
time.sleep(1)
except Exception as e:
- print >> sys.stderr, "file monitor crashed:", str(e)
+ print("file monitor crashed:", str(e), file=sys.stderr)
else:
# print "file monitor: done with", filename
pass
@@ -79,7 +81,7 @@ class ExternalEditor(threading.Thread):
if __name__ == '__main__':
def p(data):
- print data
+ print(data)
e = ExternalEditor('/usr/bin/gedit', "test", "content", p)
e.open_editor()
diff --git a/grc/main.py b/grc/main.py
index 0edab40769..ff0811e22a 100755
--- a/grc/main.py
+++ b/grc/main.py
@@ -17,7 +17,10 @@
import argparse
-import gtk
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
from gnuradio import gr
from .gui.Platform import Platform
@@ -40,7 +43,7 @@ def main():
args = parser.parse_args()
try:
- gtk.window_set_default_icon(gtk.IconTheme().load_icon('gnuradio-grc', 256, 0))
+ Gtk.window_set_default_icon(Gtk.IconTheme().load_icon('gnuradio-grc', 256, 0))
except:
pass
@@ -51,5 +54,5 @@ def main():
install_prefix=gr.prefix()
)
ActionHandler(args.flow_graphs, platform)
- gtk.main()
+ Gtk.main()