mirror of
https://github.com/gnuradio/gnuradio-companion.git
synced 2025-12-10 17:46:12 -06:00
In #6888 the colors that indicate the type of each field in the block properties were replaced by labels, because colors can give contrast problems, specially in dark mode. I personally find colors useful, because they help me to identify quickly the field that I want to edit (perhaps I'm just too used to having colors). This commit adds the field colors back, but only as an optional feature controlled by an entry in the View menu. This setting is saved to the GRC preferences, and it defaults to colors disabled. Users that prefer colors can simply enable the old behavior in this menu entry. The new labels to the right of each field that indicate the field type are always shown, regardless of whether field colors are enabled. Even when colors are shown, the labels can be a useful reminder of what type each color stands for, and they don't use too much screen real state. Signed-off-by: Daniel Estévez <daniel@destevez.net>
901 lines
40 KiB
Python
901 lines
40 KiB
Python
"""
|
|
Copyright 2007-2011 Free Software Foundation, Inc.
|
|
This file is part of GNU Radio
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
"""
|
|
|
|
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
|
|
from gi.repository import Gtk, Gio, GLib, GObject
|
|
from getpass import getuser
|
|
|
|
from . import Constants, Dialogs, Actions, Executor, FileDialogs, Utils, Bars
|
|
|
|
from .MainWindow import MainWindow
|
|
# from .ParserErrorsDialog import ParserErrorsDialog
|
|
from .PropsDialog import PropsDialog
|
|
|
|
from ..core import Messages
|
|
from ..core.Connection import Connection
|
|
from ..core.blocks import Block
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class Application(Gtk.Application):
|
|
"""
|
|
The action handler will setup all the major window components,
|
|
and handle button presses and flow graph operations from the GUI.
|
|
"""
|
|
|
|
def __init__(self, file_paths, platform):
|
|
Gtk.Application.__init__(self)
|
|
"""
|
|
Application constructor.
|
|
Create the main window, setup the message handler, import the preferences,
|
|
and connect all of the action handlers. Finally, enter the gtk main loop and block.
|
|
|
|
Args:
|
|
file_paths: a list of flow graph file passed from command line
|
|
platform: platform module
|
|
"""
|
|
self.clipboard = None
|
|
self.dialog = None
|
|
|
|
# Setup the main window
|
|
self.platform = platform
|
|
self.config = platform.config
|
|
|
|
log.debug("Application()")
|
|
# Connect all actions to _handle_action
|
|
for x in Actions.get_actions():
|
|
Actions.connect(x, handler=self._handle_action)
|
|
Actions.actions[x].enable()
|
|
if x.startswith("app."):
|
|
self.add_action(Actions.actions[x])
|
|
# Setup the shortcut keys
|
|
# These are the globally defined shortcuts
|
|
keypress = Actions.actions[x].keypresses
|
|
if keypress:
|
|
self.set_accels_for_action(x, keypress)
|
|
|
|
# Initialize
|
|
self.init_file_paths = [os.path.abspath(
|
|
file_path) for file_path in file_paths]
|
|
self.init = False
|
|
|
|
def do_startup(self):
|
|
Gtk.Application.do_startup(self)
|
|
log.debug("Application.do_startup()")
|
|
|
|
def do_activate(self):
|
|
Gtk.Application.do_activate(self)
|
|
log.debug("Application.do_activate()")
|
|
|
|
self.main_window = MainWindow(self, self.platform)
|
|
self.main_window.connect('delete-event', self._quit)
|
|
self.get_focus_flag = self.main_window.get_focus_flag
|
|
|
|
# setup the messages
|
|
Messages.register_messenger(self.main_window.add_console_line)
|
|
Messages.send_init(self.platform)
|
|
|
|
log.debug("Calling Actions.APPLICATION_INITIALIZE")
|
|
Actions.APPLICATION_INITIALIZE()
|
|
|
|
def _quit(self, window, event):
|
|
"""
|
|
Handle the delete event from the main window.
|
|
Generated by pressing X to close, alt+f4, or right click+close.
|
|
This method in turns calls the state handler to quit.
|
|
|
|
Returns:
|
|
true
|
|
"""
|
|
Actions.APPLICATION_QUIT()
|
|
return True
|
|
|
|
def _handle_action(self, action, *args):
|
|
log.debug("_handle_action({0}, {1})".format(action, args))
|
|
main = self.main_window
|
|
page = main.current_page
|
|
flow_graph = page.flow_graph if page else None
|
|
|
|
def flow_graph_update(fg=flow_graph):
|
|
main.vars.update_gui(fg.blocks)
|
|
fg.update()
|
|
|
|
##################################################
|
|
# Initialize/Quit
|
|
##################################################
|
|
if action == Actions.APPLICATION_INITIALIZE:
|
|
log.debug("APPLICATION_INITIALIZE")
|
|
file_path_to_show = self.config.file_open()
|
|
for file_path in (self.init_file_paths or self.config.get_open_files()):
|
|
if os.path.exists(file_path):
|
|
main.new_page(
|
|
file_path, show=file_path_to_show == file_path)
|
|
if not main.current_page:
|
|
main.new_page() # ensure that at least a blank page exists
|
|
|
|
main.btwin.search_entry.hide()
|
|
|
|
"""
|
|
Only disable certain actions on startup. Each of these actions are
|
|
conditionally enabled in _handle_action, so disable them first.
|
|
- FLOW_GRAPH_UNDO/REDO are set in gui/StateCache.py
|
|
- XML_PARSER_ERRORS_DISPLAY is set in RELOAD_BLOCKS
|
|
|
|
TODO: These 4 should probably be included, but they are not currently
|
|
enabled anywhere else:
|
|
- PORT_CONTROLLER_DEC, PORT_CONTROLLER_INC
|
|
- BLOCK_INC_TYPE, BLOCK_DEC_TYPE
|
|
|
|
TODO: These should be handled better. They are set in
|
|
update_exec_stop(), but not anywhere else
|
|
- FLOW_GRAPH_GEN, FLOW_GRAPH_EXEC, FLOW_GRAPH_KILL
|
|
"""
|
|
for action in (
|
|
Actions.ERRORS_WINDOW_DISPLAY,
|
|
Actions.ELEMENT_DELETE,
|
|
Actions.BLOCK_PARAM_MODIFY,
|
|
Actions.BLOCK_ROTATE_CCW,
|
|
Actions.BLOCK_ROTATE_CW,
|
|
Actions.BLOCK_VALIGN_TOP,
|
|
Actions.BLOCK_VALIGN_MIDDLE,
|
|
Actions.BLOCK_VALIGN_BOTTOM,
|
|
Actions.BLOCK_HALIGN_LEFT,
|
|
Actions.BLOCK_HALIGN_CENTER,
|
|
Actions.BLOCK_HALIGN_RIGHT,
|
|
Actions.BLOCK_CUT,
|
|
Actions.BLOCK_COPY,
|
|
Actions.BLOCK_PASTE,
|
|
Actions.BLOCK_ENABLE,
|
|
Actions.BLOCK_DISABLE,
|
|
Actions.BLOCK_BYPASS,
|
|
Actions.BLOCK_CREATE_HIER,
|
|
Actions.OPEN_HIER,
|
|
Actions.BUSSIFY_SOURCES,
|
|
Actions.BUSSIFY_SINKS,
|
|
Actions.FLOW_GRAPH_SAVE,
|
|
Actions.FLOW_GRAPH_UNDO,
|
|
Actions.FLOW_GRAPH_REDO,
|
|
Actions.XML_PARSER_ERRORS_DISPLAY
|
|
):
|
|
action.disable()
|
|
|
|
# Load preferences
|
|
for action in (
|
|
Actions.TOGGLE_BLOCKS_WINDOW,
|
|
Actions.TOGGLE_CONSOLE_WINDOW,
|
|
Actions.TOGGLE_HIDE_DISABLED_BLOCKS,
|
|
Actions.TOGGLE_SCROLL_LOCK,
|
|
Actions.TOGGLE_AUTO_HIDE_PORT_LABELS,
|
|
Actions.TOGGLE_SNAP_TO_GRID,
|
|
Actions.TOGGLE_SHOW_BLOCK_COMMENTS,
|
|
Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB,
|
|
Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY,
|
|
Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR,
|
|
Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR,
|
|
Actions.TOGGLE_HIDE_VARIABLES,
|
|
Actions.TOGGLE_SHOW_PARAMETER_EXPRESSION,
|
|
Actions.TOGGLE_SHOW_PARAMETER_EVALUATION,
|
|
Actions.TOGGLE_SHOW_BLOCK_IDS,
|
|
Actions.TOGGLE_SHOW_FIELD_COLORS,
|
|
):
|
|
action.set_enabled(True)
|
|
if hasattr(action, 'load_from_preferences'):
|
|
action.load_from_preferences()
|
|
|
|
# Hide the panels *IF* it's saved in preferences
|
|
main.update_panel_visibility(
|
|
main.BLOCKS, Actions.TOGGLE_BLOCKS_WINDOW.get_active())
|
|
main.update_panel_visibility(
|
|
main.CONSOLE, Actions.TOGGLE_CONSOLE_WINDOW.get_active())
|
|
main.update_panel_visibility(
|
|
main.VARIABLES, Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR.get_active())
|
|
|
|
# Force an update on the current page to match loaded preferences.
|
|
# In the future, change the __init__ order to load preferences first
|
|
page = main.current_page
|
|
if page:
|
|
page.flow_graph.update()
|
|
|
|
self.init = True
|
|
elif action == Actions.APPLICATION_QUIT:
|
|
if main.close_pages():
|
|
while Gtk.main_level():
|
|
Gtk.main_quit()
|
|
exit(0)
|
|
##################################################
|
|
# Selections
|
|
##################################################
|
|
elif action == Actions.ELEMENT_SELECT:
|
|
pass # do nothing, update routines below
|
|
elif action == Actions.NOTHING_SELECT:
|
|
flow_graph.unselect()
|
|
elif action == Actions.SELECT_ALL:
|
|
if main.btwin.search_entry.has_focus():
|
|
main.btwin.search_entry.select_region(0, -1)
|
|
else:
|
|
flow_graph.select_all()
|
|
##################################################
|
|
# Enable/Disable
|
|
##################################################
|
|
elif action in (Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE, Actions.BLOCK_BYPASS):
|
|
changed = flow_graph.change_state_selected(new_state={
|
|
Actions.BLOCK_ENABLE: 'enabled',
|
|
Actions.BLOCK_DISABLE: 'disabled',
|
|
Actions.BLOCK_BYPASS: 'bypassed',
|
|
}[action])
|
|
if changed:
|
|
flow_graph_update()
|
|
page.state_cache.save_new_state(flow_graph.export_data())
|
|
page.saved = False
|
|
##################################################
|
|
# Cut/Copy/Paste
|
|
##################################################
|
|
elif action == Actions.BLOCK_CUT:
|
|
Actions.BLOCK_COPY()
|
|
Actions.ELEMENT_DELETE()
|
|
elif action == Actions.BLOCK_COPY:
|
|
self.clipboard = flow_graph.copy_to_clipboard()
|
|
elif action == Actions.BLOCK_PASTE:
|
|
if self.clipboard:
|
|
flow_graph.paste_from_clipboard(self.clipboard)
|
|
flow_graph_update()
|
|
page.state_cache.save_new_state(flow_graph.export_data())
|
|
page.saved = False
|
|
##################################################
|
|
# Create hier block
|
|
##################################################
|
|
elif action == Actions.BLOCK_CREATE_HIER:
|
|
|
|
selected_blocks = []
|
|
|
|
pads = []
|
|
params = set()
|
|
|
|
for block in flow_graph.selected_blocks():
|
|
selected_blocks.append(block)
|
|
# Check for string variables within the blocks
|
|
for param in block.params.values():
|
|
for variable in flow_graph.get_variables():
|
|
# If a block parameter exists that is a variable, create a parameter for it
|
|
if param.get_value() == variable.name:
|
|
params.add(param.get_value())
|
|
for flow_param in flow_graph.get_parameters():
|
|
# If a block parameter exists that is a parameter, create a parameter for it
|
|
if param.get_value() == flow_param.name:
|
|
params.add(param.get_value())
|
|
|
|
x_min = min(block.coordinate[0] for block in selected_blocks)
|
|
y_min = min(block.coordinate[1] for block in selected_blocks)
|
|
|
|
for connection in flow_graph.connections:
|
|
|
|
# Get id of connected blocks
|
|
source = connection.source_block
|
|
sink = connection.sink_block
|
|
|
|
if source not in selected_blocks and sink in selected_blocks:
|
|
# Create Pad Source
|
|
pads.append({
|
|
'key': connection.sink_port.key,
|
|
'coord': source.coordinate,
|
|
# Ignore the options block
|
|
'block_index': selected_blocks.index(sink) + 1,
|
|
'direction': 'source'
|
|
})
|
|
|
|
elif sink not in selected_blocks and source in selected_blocks:
|
|
# Create Pad Sink
|
|
pads.append({
|
|
'key': connection.source_port.key,
|
|
'coord': sink.coordinate,
|
|
# Ignore the options block
|
|
'block_index': selected_blocks.index(source) + 1,
|
|
'direction': 'sink'
|
|
})
|
|
|
|
# Copy the selected blocks and paste them into a new page
|
|
# then move the flowgraph to a reasonable position
|
|
Actions.BLOCK_COPY()
|
|
main.new_page()
|
|
flow_graph = main.current_page.flow_graph
|
|
Actions.BLOCK_PASTE()
|
|
coords = (x_min, y_min)
|
|
flow_graph.move_selected(coords)
|
|
|
|
# Set flow graph to heir block type
|
|
top_block = flow_graph.get_block(Constants.DEFAULT_FLOW_GRAPH_ID)
|
|
top_block.params['generate_options'].set_value('hb')
|
|
|
|
# this needs to be a unique name
|
|
top_block.params['id'].set_value('new_hier')
|
|
|
|
# Remove the default samp_rate variable block that is created
|
|
remove_me = flow_graph.get_block("samp_rate")
|
|
flow_graph.remove_element(remove_me)
|
|
|
|
# Add the param blocks along the top of the window
|
|
x_pos = 150
|
|
for param in params:
|
|
param_id = flow_graph.add_new_block('parameter', (x_pos, 10))
|
|
param_block = flow_graph.get_block(param_id)
|
|
param_block.params['id'].set_value(param)
|
|
x_pos = x_pos + 100
|
|
|
|
for pad in pads:
|
|
# add the pad sources and sinks within the new hier block
|
|
if pad['direction'] == 'sink':
|
|
|
|
# add new pad_sink block to the canvas
|
|
pad_id = flow_graph.add_new_block('pad_sink', pad['coord'])
|
|
|
|
# setup the references to the sink and source
|
|
pad_block = flow_graph.get_block(pad_id)
|
|
pad_sink = pad_block.sinks[0]
|
|
|
|
source_block = flow_graph.get_block(
|
|
flow_graph.blocks[pad['block_index']].name)
|
|
source = source_block.get_source(pad['key'])
|
|
|
|
# ensure the port types match
|
|
if pad_sink.dtype != source.dtype:
|
|
if pad_sink.dtype == 'complex' and source.dtype == 'fc32':
|
|
pass
|
|
else:
|
|
pad_block.params['type'].value = source.dtype
|
|
pad_sink.dtype = source.dtype
|
|
|
|
# connect the pad to the proper sinks
|
|
new_connection = flow_graph.connect(source, pad_sink)
|
|
|
|
elif pad['direction'] == 'source':
|
|
pad_id = flow_graph.add_new_block(
|
|
'pad_source', pad['coord'])
|
|
|
|
# setup the references to the sink and source
|
|
pad_block = flow_graph.get_block(pad_id)
|
|
pad_source = pad_block.sources[0]
|
|
|
|
sink_block = flow_graph.get_block(
|
|
flow_graph.blocks[pad['block_index']].name)
|
|
sink = sink_block.get_sink(pad['key'])
|
|
|
|
# ensure the port types match
|
|
if pad_source.dtype != sink.dtype:
|
|
if pad_source.dtype == 'complex' and sink.dtype == 'fc32':
|
|
pass
|
|
else:
|
|
pad_block.params['type'].value = sink.dtype
|
|
pad_source.dtype = sink.dtype
|
|
|
|
# connect the pad to the proper sinks
|
|
new_connection = flow_graph.connect(pad_source, sink)
|
|
|
|
flow_graph_update(flow_graph)
|
|
##################################################
|
|
# Move/Rotate/Delete/Create
|
|
##################################################
|
|
elif action == Actions.BLOCK_MOVE:
|
|
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.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.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.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.state_cache.save_new_state(flow_graph.export_data())
|
|
Actions.NOTHING_SELECT()
|
|
page.saved = False
|
|
elif action == Actions.ELEMENT_CREATE:
|
|
flow_graph_update()
|
|
page.state_cache.save_new_state(flow_graph.export_data())
|
|
Actions.NOTHING_SELECT()
|
|
page.saved = False
|
|
elif action == Actions.BLOCK_INC_TYPE:
|
|
if flow_graph.type_controller_modify_selected(1):
|
|
flow_graph_update()
|
|
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.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.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.state_cache.save_new_state(flow_graph.export_data())
|
|
page.saved = False
|
|
##################################################
|
|
# Window stuff
|
|
##################################################
|
|
elif action == Actions.ABOUT_WINDOW_DISPLAY:
|
|
Dialogs.show_about(main, self.platform.config)
|
|
elif action == Actions.HELP_WINDOW_DISPLAY:
|
|
Dialogs.show_help(main)
|
|
elif action == Actions.GET_INVOLVED_WINDOW_DISPLAY:
|
|
Dialogs.show_get_involved(main)
|
|
elif action == Actions.TYPES_WINDOW_DISPLAY:
|
|
Dialogs.show_types(main)
|
|
elif action == Actions.KEYBOARD_SHORTCUTS_WINDOW_DISPLAY:
|
|
Dialogs.show_keyboard_shortcuts(main)
|
|
elif action == Actions.ERRORS_WINDOW_DISPLAY:
|
|
Dialogs.ErrorsDialog(main, flow_graph).run_and_destroy()
|
|
elif action == Actions.TOGGLE_CONSOLE_WINDOW:
|
|
action.set_active(not action.get_active())
|
|
main.update_panel_visibility(main.CONSOLE, action.get_active())
|
|
action.save_to_preferences()
|
|
elif action == Actions.TOGGLE_BLOCKS_WINDOW:
|
|
# This would be better matched to a Gio.PropertyAction, but to do
|
|
# this, actions would have to be defined in the window not globally
|
|
action.set_active(not action.get_active())
|
|
main.update_panel_visibility(main.BLOCKS, action.get_active())
|
|
action.save_to_preferences()
|
|
elif action == Actions.TOGGLE_SCROLL_LOCK:
|
|
action.set_active(not action.get_active())
|
|
active = action.get_active()
|
|
main.console.text_display.scroll_lock = active
|
|
if active:
|
|
main.console.text_display.scroll_to_end()
|
|
action.save_to_preferences()
|
|
elif action == Actions.CLEAR_CONSOLE:
|
|
main.console.text_display.clear()
|
|
elif action == Actions.SAVE_CONSOLE:
|
|
file_path = FileDialogs.SaveConsole(main, page.file_path).run()
|
|
if file_path is not None:
|
|
main.console.text_display.save(file_path)
|
|
elif action == Actions.TOGGLE_HIDE_DISABLED_BLOCKS:
|
|
action.set_active(not action.get_active())
|
|
flow_graph_update()
|
|
action.save_to_preferences()
|
|
page.state_cache.save_new_state(flow_graph.export_data())
|
|
Actions.NOTHING_SELECT()
|
|
elif action == Actions.TOGGLE_AUTO_HIDE_PORT_LABELS:
|
|
action.set_active(not action.get_active())
|
|
action.save_to_preferences()
|
|
for page in main.get_pages():
|
|
page.flow_graph.create_shapes()
|
|
elif action in (Actions.TOGGLE_SNAP_TO_GRID,
|
|
Actions.TOGGLE_SHOW_BLOCK_COMMENTS,
|
|
Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB):
|
|
action.set_active(not action.get_active())
|
|
action.save_to_preferences()
|
|
elif action == Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY:
|
|
action.set_active(not action.get_active())
|
|
action.save_to_preferences()
|
|
for page in main.get_pages():
|
|
flow_graph_update(page.flow_graph)
|
|
elif action == Actions.TOGGLE_SHOW_PARAMETER_EXPRESSION:
|
|
action.set_active(not action.get_active())
|
|
action.save_to_preferences()
|
|
flow_graph_update()
|
|
elif action == Actions.TOGGLE_SHOW_PARAMETER_EVALUATION:
|
|
action.set_active(not action.get_active())
|
|
action.save_to_preferences()
|
|
flow_graph_update()
|
|
elif action == Actions.TOGGLE_HIDE_VARIABLES:
|
|
action.set_active(not action.get_active())
|
|
active = action.get_active()
|
|
# Either way, triggering this should simply trigger the variable editor
|
|
# to be visible.
|
|
varedit = Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR
|
|
if active:
|
|
log.debug(
|
|
"Variables are hidden. Forcing the variable panel to be visible.")
|
|
varedit.disable()
|
|
else:
|
|
varedit.enable()
|
|
# Just force it to show.
|
|
varedit.set_active(True)
|
|
main.update_panel_visibility(main.VARIABLES)
|
|
Actions.NOTHING_SELECT()
|
|
action.save_to_preferences()
|
|
varedit.save_to_preferences()
|
|
flow_graph_update()
|
|
elif action == Actions.TOGGLE_SHOW_BLOCK_IDS:
|
|
action.set_active(not action.get_active())
|
|
active = action.get_active()
|
|
Actions.NOTHING_SELECT()
|
|
action.save_to_preferences()
|
|
flow_graph_update()
|
|
elif action == Actions.TOGGLE_SHOW_FIELD_COLORS:
|
|
action.set_active(not action.get_active())
|
|
action.save_to_preferences()
|
|
elif action == Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR:
|
|
# TODO: There may be issues at startup since these aren't triggered
|
|
# the same was as Gtk.Actions when loading preferences.
|
|
action.set_active(not action.get_active())
|
|
# Just assume this was triggered because it was enabled.
|
|
main.update_panel_visibility(main.VARIABLES, action.get_active())
|
|
action.save_to_preferences()
|
|
|
|
# Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR.set_enabled(action.get_active())
|
|
action.save_to_preferences()
|
|
elif action == Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR:
|
|
action.set_active(not action.get_active())
|
|
if self.init:
|
|
Dialogs.MessageDialogWrapper(
|
|
main, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE,
|
|
markup="Moving the variable editor requires a restart of GRC."
|
|
).run_and_destroy()
|
|
action.save_to_preferences()
|
|
elif action == Actions.ZOOM_IN:
|
|
page.drawing_area.zoom_in()
|
|
elif action == Actions.ZOOM_OUT:
|
|
page.drawing_area.zoom_out()
|
|
elif action == Actions.ZOOM_RESET:
|
|
page.drawing_area.reset_zoom()
|
|
##################################################
|
|
# Param Modifications
|
|
##################################################
|
|
elif action == Actions.BLOCK_PARAM_MODIFY:
|
|
selected_block = args[0] if args[0] else flow_graph.selected_block
|
|
selected_conn = args[0] if args[0] else flow_graph.selected_connection
|
|
if selected_block and isinstance(selected_block, Block):
|
|
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.ResponseType.APPLY, Gtk.ResponseType.ACCEPT):
|
|
page.state_cache.save_new_state(
|
|
flow_graph.export_data())
|
|
# Following line forces a complete update of io ports
|
|
flow_graph_update()
|
|
page.saved = False
|
|
if response in (Gtk.ResponseType.REJECT, Gtk.ResponseType.ACCEPT):
|
|
n = page.state_cache.get_current_state()
|
|
flow_graph.import_data(n)
|
|
flow_graph_update()
|
|
if response == Gtk.ResponseType.APPLY:
|
|
# null action, that updates the main window
|
|
Actions.ELEMENT_SELECT()
|
|
self.dialog.destroy()
|
|
self.dialog = None
|
|
elif selected_conn and isinstance(selected_conn, Connection):
|
|
self.dialog = PropsDialog(self.main_window, selected_conn)
|
|
response = Gtk.ResponseType.APPLY
|
|
while response == Gtk.ResponseType.APPLY: # rerun the dialog if Apply was hit
|
|
response = self.dialog.run()
|
|
if response in (Gtk.ResponseType.APPLY, Gtk.ResponseType.ACCEPT):
|
|
page.state_cache.save_new_state(
|
|
flow_graph.export_data())
|
|
# Following line forces a complete update of io ports
|
|
flow_graph_update()
|
|
page.saved = False
|
|
if response in (Gtk.ResponseType.REJECT, Gtk.ResponseType.ACCEPT):
|
|
curr_state = page.state_cache.get_current_state()
|
|
flow_graph.import_data(curr_state)
|
|
flow_graph_update()
|
|
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.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.saved = False
|
|
elif action == Actions.VARIABLE_EDITOR_UPDATE:
|
|
page.state_cache.save_new_state(flow_graph.export_data())
|
|
flow_graph_update()
|
|
page.saved = False
|
|
##################################################
|
|
# View Parser Errors
|
|
##################################################
|
|
elif action == Actions.XML_PARSER_ERRORS_DISPLAY:
|
|
pass
|
|
##################################################
|
|
# Undo/Redo
|
|
##################################################
|
|
elif action == Actions.FLOW_GRAPH_UNDO:
|
|
n = page.state_cache.get_prev_state()
|
|
if n:
|
|
flow_graph.unselect()
|
|
flow_graph.import_data(n)
|
|
flow_graph_update()
|
|
page.saved = False
|
|
elif action == Actions.FLOW_GRAPH_REDO:
|
|
n = page.state_cache.get_next_state()
|
|
if n:
|
|
flow_graph.unselect()
|
|
flow_graph.import_data(n)
|
|
flow_graph_update()
|
|
page.saved = False
|
|
##################################################
|
|
# New/Open/Save/Close
|
|
##################################################
|
|
elif action == Actions.FLOW_GRAPH_NEW:
|
|
main.new_page()
|
|
args = (GLib.Variant('s', 'qt_gui'),)
|
|
flow_graph = main.current_page.flow_graph
|
|
flow_graph.options_block.params['generate_options'].set_value(args[0].get_string())
|
|
flow_graph.options_block.params['author'].set_value(getuser())
|
|
flow_graph_update(flow_graph)
|
|
elif action == Actions.FLOW_GRAPH_NEW_TYPE:
|
|
main.new_page()
|
|
if args:
|
|
flow_graph = main.current_page.flow_graph
|
|
flow_graph.options_block.params['generate_options'].set_value(args[0].get_string())
|
|
flow_graph_update(flow_graph)
|
|
elif action == Actions.FLOW_GRAPH_OPEN:
|
|
file_paths = args[0] if args[0] else FileDialogs.OpenFlowGraph(
|
|
main, 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))
|
|
self.config.add_recent_file(file_path)
|
|
main.tool_bar.refresh_submenus()
|
|
main.menu.refresh_submenus()
|
|
elif action == Actions.FLOW_GRAPH_OPEN_QSS_THEME:
|
|
file_paths = FileDialogs.OpenQSS(main, self.platform.config.install_prefix +
|
|
'/share/gnuradio/themes/').run()
|
|
if file_paths:
|
|
self.platform.config.default_qss_theme = file_paths[0]
|
|
elif action == Actions.FLOW_GRAPH_CLOSE:
|
|
main.close_page()
|
|
elif action == Actions.FLOW_GRAPH_OPEN_RECENT:
|
|
file_path = args[0].get_string()
|
|
main.new_page(file_path, show=True)
|
|
self.config.add_recent_file(file_path)
|
|
main.tool_bar.refresh_submenus()
|
|
main.menu.refresh_submenus()
|
|
elif action == Actions.FLOW_GRAPH_SAVE:
|
|
# read-only or undefined file path, do save-as
|
|
if page.get_read_only() or not page.file_path:
|
|
Actions.FLOW_GRAPH_SAVE_AS()
|
|
# otherwise try to save
|
|
else:
|
|
try:
|
|
self.platform.save_flow_graph(page.file_path, flow_graph)
|
|
flow_graph.grc_file_path = page.file_path
|
|
page.saved = True
|
|
except IOError:
|
|
Messages.send_fail_save(page.file_path)
|
|
page.saved = False
|
|
elif action == Actions.FLOW_GRAPH_SAVE_AS:
|
|
file_path = FileDialogs.SaveFlowGraph(main, page.file_path).run()
|
|
|
|
if file_path is not None:
|
|
if flow_graph.options_block.params['id'].get_value() == Constants.DEFAULT_FLOW_GRAPH_ID:
|
|
file_name = os.path.basename(file_path).replace(".grc", "")
|
|
flow_graph.options_block.params['id'].set_value(file_name)
|
|
flow_graph_update(flow_graph)
|
|
|
|
page.file_path = os.path.abspath(file_path)
|
|
try:
|
|
self.platform.save_flow_graph(page.file_path, flow_graph)
|
|
flow_graph.grc_file_path = page.file_path
|
|
page.saved = True
|
|
except IOError:
|
|
Messages.send_fail_save(page.file_path)
|
|
page.saved = False
|
|
self.config.add_recent_file(file_path)
|
|
main.tool_bar.refresh_submenus()
|
|
main.menu.refresh_submenus()
|
|
elif action == Actions.FLOW_GRAPH_SAVE_COPY:
|
|
try:
|
|
if not page.file_path:
|
|
# Make sure the current flowgraph has been saved
|
|
Actions.FLOW_GRAPH_SAVE_AS()
|
|
else:
|
|
dup_file_path = page.file_path
|
|
# Assuming .grc extension at the end of file_path
|
|
dup_file_name = '.'.join(
|
|
dup_file_path.split('.')[:-1]) + "_copy"
|
|
dup_file_path_temp = dup_file_name + Constants.FILE_EXTENSION
|
|
count = 1
|
|
while os.path.exists(dup_file_path_temp):
|
|
dup_file_path_temp = '{}({}){}'.format(
|
|
dup_file_name, count, Constants.FILE_EXTENSION)
|
|
count += 1
|
|
dup_file_path_user = FileDialogs.SaveFlowGraph(
|
|
main, dup_file_path_temp).run()
|
|
if dup_file_path_user is not None:
|
|
self.platform.save_flow_graph(
|
|
dup_file_path_user, flow_graph)
|
|
Messages.send('Saved Copy to: "' +
|
|
dup_file_path_user + '"\n')
|
|
except IOError:
|
|
Messages.send_fail_save(
|
|
"Can not create a copy of the flowgraph\n")
|
|
elif action == Actions.FLOW_GRAPH_DUPLICATE:
|
|
previous = flow_graph
|
|
# Create a new page
|
|
main.new_page()
|
|
page = main.current_page
|
|
new_flow_graph = page.flow_graph
|
|
# Import the old data and mark the current as not saved
|
|
new_flow_graph.import_data(previous.export_data())
|
|
flow_graph_update(new_flow_graph)
|
|
page.state_cache.save_new_state(new_flow_graph.export_data())
|
|
page.saved = False
|
|
elif action == Actions.FLOW_GRAPH_SCREEN_CAPTURE:
|
|
file_path, background_transparent = FileDialogs.SaveScreenShot(
|
|
main, page.file_path).run()
|
|
if file_path is not None:
|
|
try:
|
|
Utils.make_screenshot(
|
|
flow_graph, file_path, background_transparent)
|
|
except ValueError:
|
|
Messages.send('Failed to generate screen shot\n')
|
|
##################################################
|
|
# Gen/Exec/Stop
|
|
##################################################
|
|
elif action == Actions.FLOW_GRAPH_GEN:
|
|
self.generator = None
|
|
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.file_path)
|
|
generator.write()
|
|
self.generator = generator
|
|
except Exception as e:
|
|
Messages.send_fail_gen(e)
|
|
|
|
elif action == Actions.FLOW_GRAPH_EXEC:
|
|
if not page.process:
|
|
Actions.FLOW_GRAPH_GEN()
|
|
if self.generator:
|
|
xterm = self.platform.config.xterm_executable
|
|
if self.config.xterm_missing() != xterm:
|
|
if not os.path.exists(xterm):
|
|
Dialogs.show_missing_xterm(main, xterm)
|
|
self.config.xterm_missing(xterm)
|
|
if page.saved and page.file_path:
|
|
# Save config before execution
|
|
self.config.save()
|
|
Executor.ExecFlowGraphThread(
|
|
flow_graph_page=page,
|
|
xterm_executable=xterm,
|
|
callback=self.update_exec_stop
|
|
)
|
|
elif action == Actions.FLOW_GRAPH_KILL:
|
|
if page.process:
|
|
try:
|
|
page.process.terminate()
|
|
except OSError:
|
|
print("could not terminate process: %d" % page.process.pid)
|
|
|
|
elif action == Actions.PAGE_CHANGE: # pass and run the global actions
|
|
flow_graph_update()
|
|
elif action == Actions.RELOAD_BLOCKS:
|
|
self.platform.build_library()
|
|
main.btwin.repopulate()
|
|
|
|
# todo: implement parser error dialog for YAML
|
|
|
|
# Force a redraw of the graph, by getting the current state and re-importing it
|
|
main.update_pages()
|
|
|
|
elif action == Actions.FIND_BLOCKS:
|
|
main.update_panel_visibility(main.BLOCKS, True)
|
|
main.btwin.search_entry.show()
|
|
main.btwin.search_entry.grab_focus()
|
|
elif action == Actions.OPEN_HIER:
|
|
for b in flow_graph.selected_blocks():
|
|
grc_source = b.extra_data.get('grc_source', '')
|
|
if grc_source:
|
|
main.new_page(grc_source, show=True)
|
|
elif action == Actions.BUSSIFY_SOURCES:
|
|
for b in flow_graph.selected_blocks():
|
|
b.bussify('source')
|
|
flow_graph._old_selected_port = None
|
|
flow_graph._new_selected_port = None
|
|
Actions.ELEMENT_CREATE()
|
|
|
|
elif action == Actions.BUSSIFY_SINKS:
|
|
for b in flow_graph.selected_blocks():
|
|
b.bussify('sink')
|
|
flow_graph._old_selected_port = None
|
|
flow_graph._new_selected_port = None
|
|
Actions.ELEMENT_CREATE()
|
|
|
|
elif action == Actions.TOOLS_RUN_FDESIGN:
|
|
subprocess.Popen('gr_filter_design',
|
|
shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
|
else:
|
|
log.warning('!!! Action "%s" not handled !!!' % action)
|
|
##################################################
|
|
# Global Actions for all States
|
|
##################################################
|
|
page = main.current_page # page and flow graph might have changed
|
|
flow_graph = page.flow_graph if page else None
|
|
|
|
selected_blocks = list(flow_graph.selected_blocks())
|
|
selected_block = selected_blocks[0] if selected_blocks else None
|
|
# See if a connection has modifiable parameters or grey out the entry
|
|
# in the menu
|
|
selected_connections = list(flow_graph.selected_connections())
|
|
selected_connection = selected_connections[0] \
|
|
if len(selected_connections) == 1 \
|
|
else None
|
|
selected_conn_has_params = selected_connection and bool(len(selected_connection.params))
|
|
|
|
# update general buttons
|
|
Actions.ERRORS_WINDOW_DISPLAY.set_enabled(not flow_graph.is_valid())
|
|
Actions.ELEMENT_DELETE.set_enabled(bool(flow_graph.selected_elements))
|
|
Actions.BLOCK_PARAM_MODIFY.set_enabled(bool(selected_block) or bool(selected_conn_has_params))
|
|
Actions.BLOCK_ROTATE_CCW.set_enabled(bool(selected_blocks))
|
|
Actions.BLOCK_ROTATE_CW.set_enabled(bool(selected_blocks))
|
|
# update alignment options
|
|
for act in Actions.BLOCK_ALIGNMENTS:
|
|
if act:
|
|
act.set_enabled(len(selected_blocks) > 1)
|
|
# update cut/copy/paste
|
|
Actions.BLOCK_CUT.set_enabled(bool(selected_blocks))
|
|
Actions.BLOCK_COPY.set_enabled(bool(selected_blocks))
|
|
Actions.BLOCK_PASTE.set_enabled(bool(self.clipboard))
|
|
# update enable/disable/bypass
|
|
can_enable = any(block.state != 'enabled'
|
|
for block in selected_blocks)
|
|
can_disable = any(block.state != '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)
|
|
)
|
|
Actions.BLOCK_ENABLE.set_enabled(can_enable)
|
|
Actions.BLOCK_DISABLE.set_enabled(can_disable)
|
|
Actions.BLOCK_BYPASS.set_enabled(can_bypass_all)
|
|
|
|
Actions.BLOCK_CREATE_HIER.set_enabled(bool(selected_blocks))
|
|
Actions.OPEN_HIER.set_enabled(bool(selected_blocks))
|
|
Actions.BUSSIFY_SOURCES.set_enabled(any(block.sources for block in selected_blocks))
|
|
Actions.BUSSIFY_SINKS.set_enabled(any(block.sinks for block in selected_blocks))
|
|
Actions.RELOAD_BLOCKS.enable()
|
|
Actions.FIND_BLOCKS.enable()
|
|
|
|
self.update_exec_stop()
|
|
|
|
Actions.FLOW_GRAPH_SAVE.set_enabled(not page.saved)
|
|
main.update()
|
|
|
|
flow_graph.update_selected()
|
|
page.drawing_area.queue_draw()
|
|
|
|
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.
|
|
"""
|
|
page = self.main_window.current_page
|
|
sensitive = page.flow_graph.is_valid() and not page.process
|
|
Actions.FLOW_GRAPH_GEN.set_enabled(sensitive)
|
|
Actions.FLOW_GRAPH_EXEC.set_enabled(sensitive)
|
|
Actions.FLOW_GRAPH_KILL.set_enabled(page.process is not None)
|