diff options
author | Johnathan Corgan <johnathan@corganlabs.com> | 2015-10-03 12:01:29 -0700 |
---|---|---|
committer | Johnathan Corgan <johnathan@corganlabs.com> | 2015-10-03 12:01:29 -0700 |
commit | 73ae366a6a4d97a9d44a711a8f404c8a29bd31c9 (patch) | |
tree | aba3858dea02bd0898d3efc66b4e47488d37edfe /grc | |
parent | 779fb8fdb5bb2950efcf2a8befcb6733cb6d839d (diff) | |
parent | 0f9b29ae4cbb2e3e46abea3ab2475b784d36dab8 (diff) |
Merge branch 'master' into next
Diffstat (limited to 'grc')
-rw-r--r-- | grc/base/FlowGraph.py | 54 | ||||
-rw-r--r-- | grc/blocks/options.xml | 31 | ||||
-rw-r--r-- | grc/gui/ActionHandler.py | 65 | ||||
-rw-r--r-- | grc/gui/Actions.py | 74 | ||||
-rw-r--r-- | grc/gui/Bars.py | 115 | ||||
-rw-r--r-- | grc/gui/Block.py | 44 | ||||
-rw-r--r-- | grc/gui/Constants.py | 3 | ||||
-rw-r--r-- | grc/gui/FileDialogs.py | 15 | ||||
-rw-r--r-- | grc/gui/FlowGraph.py | 2 | ||||
-rw-r--r-- | grc/gui/MainWindow.py | 12 | ||||
-rw-r--r-- | grc/gui/Param.py | 11 | ||||
-rw-r--r-- | grc/python/Constants.py | 11 | ||||
-rw-r--r-- | grc/python/Generator.py | 14 | ||||
-rw-r--r-- | grc/python/Param.py | 40 | ||||
-rw-r--r-- | grc/python/flow_graph.tmpl | 66 |
15 files changed, 395 insertions, 162 deletions
diff --git a/grc/base/FlowGraph.py b/grc/base/FlowGraph.py index 217c4d7302..f61542c712 100644 --- a/grc/base/FlowGraph.py +++ b/grc/base/FlowGraph.py @@ -67,6 +67,58 @@ class FlowGraph(Element): def __str__(self): return 'FlowGraph - %s(%s)' % (self.get_option('title'), self.get_option('id')) + def get_complexity(self): + """ + Determines the complexity of a flowgraph + """ + dbal = 0 + block_list = self.get_blocks() + for block in block_list: + # Skip options block + if block.get_key() == 'options': + 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()) + sinks = float(len(sink_list)) + sources = float(len(source_list)) + base = max(min(sinks, sources), 1) + + # Port ratio multiplier + if min(sinks, sources) > 0: + multi = sinks / sources + multi = (1 / multi) if multi > 1 else multi + else: + 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) + + elements = float(len(self.get_elements())) + connections = float(len(self.get_connections())) + disabled_connections = len(filter(lambda c: not c.get_enabled(), self.get_connections())) + blocks = float(len(block_list)) + variables = elements - blocks - connections + enabled = float(len(self.get_enabled_blocks())) + + # Disabled multiplier + if enabled > 0: + disabled_multi = 1 / (max(1 - ((blocks - enabled) / max(blocks, 1)), 0.05)) + else: + disabled_multi = 1 + + # Connection multiplier (How many connections ) + if (connections - disabled_connections) > 0: + conn_multi = 1 / (max(1 - (disabled_connections / max(connections, 1)), 0.05)) + else: + conn_multi = 1 + + final = round(max((dbal - 1) * disabled_multi * conn_multi * connections, 0.0) / 1000000, 6) + return final + def rewrite(self): def refactor_bus_structure(): @@ -102,7 +154,7 @@ class FlowGraph(Element): get_p().append(port); for child in self.get_children(): child.rewrite() - refactor_bus_structure(); + refactor_bus_structure() def get_option(self, key): """ diff --git a/grc/blocks/options.xml b/grc/blocks/options.xml index dc02b83c2a..09cc74d151 100644 --- a/grc/blocks/options.xml +++ b/grc/blocks/options.xml @@ -77,7 +77,7 @@ else: self.stop(); self.wait()</callback> <key>hb</key> </option> <option> - <name>QT GUI Hier Block</name> + <name>Hier Block (QT GUI)</name> <key>hb_qt_gui</key> </option> </param> @@ -163,6 +163,23 @@ part#slurp </option> </param> <param> + <name>QSS Theme</name> + <key>qt_qss_theme</key> + <value></value> + <type>file_open</type> + <hide> +#if $generate_options() in ('qt_gui',) + #if $qt_qss_theme() + none + #else + part + #end if +#else + all +#end if +</hide> + </param> + <param> <name>Thread-safe setters</name> <key>thread_safe_setters</key> <value></value> @@ -178,6 +195,18 @@ part#slurp </option> <tab>Advanced</tab> </param> + <param> + <name>Run Command</name> + <key>run_command</key> + <value>{python} -u {filename}</value> + <type>string</type> + <hide>#if $generate_options().startswith('hb') +all#slurp +#else +part#slurp +#end if</hide> + <tab>Advanced</tab> + </param> <check>not $window_size or len($window_size) == 2</check> <check>not $window_size or 300 <= $(window_size)[0] <= 4096</check> <check>not $window_size or 300 <= $(window_size)[1] <= 4096</check> diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 19c6edc914..ee01595a33 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -34,8 +34,9 @@ from .ParserErrorsDialog import ParserErrorsDialog from .MainWindow import MainWindow from .PropsDialog import PropsDialog from .FileDialogs import (OpenFlowGraphFileDialog, SaveFlowGraphFileDialog, - SaveReportsFileDialog, SaveImageFileDialog) -from .Constants import DEFAULT_CANVAS_SIZE, IMAGE_FILE_EXTENSION + SaveReportsFileDialog, SaveImageFileDialog, + OpenQSSFileDialog) +from .Constants import DEFAULT_CANVAS_SIZE, IMAGE_FILE_EXTENSION, GR_PREFIX gobject.threads_init() @@ -60,7 +61,7 @@ class ActionHandler: for action in Actions.get_all_actions(): action.connect('activate', self._handle_action) #setup the main window self.platform = platform; - self.main_window = MainWindow(platform) + 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 @@ -107,27 +108,12 @@ class ActionHandler: Actions.APPLICATION_QUIT() return True - def _handle_action(self, action): + def _handle_action(self, action, *args): #print action ################################################## # Initialize/Quit ################################################## if action == Actions.APPLICATION_INITIALIZE: - for action in Actions.get_all_actions(): action.set_sensitive(False) #set all actions disabled - #enable a select few actions - for action in ( - Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW, - Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS, - Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY, - Actions.FLOW_GRAPH_SCREEN_CAPTURE, Actions.HELP_WINDOW_DISPLAY, - Actions.TYPES_WINDOW_DISPLAY, Actions.TOGGLE_BLOCKS_WINDOW, - Actions.TOGGLE_REPORTS_WINDOW, Actions.TOGGLE_HIDE_DISABLED_BLOCKS, - Actions.TOOLS_RUN_FDESIGN, Actions.TOGGLE_SCROLL_LOCK, - Actions.CLEAR_REPORTS, Actions.SAVE_REPORTS, - Actions.TOGGLE_AUTO_HIDE_PORT_LABELS, Actions.TOGGLE_SNAP_TO_GRID, - Actions.TOGGLE_SHOW_BLOCK_COMMENTS, - Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB, - ): action.set_sensitive(True) if ParseXML.xml_failures: Messages.send_xml_errors_if_any(ParseXML.xml_failures) Actions.XML_PARSER_ERRORS_DISPLAY.set_sensitive(True) @@ -142,15 +128,28 @@ class ActionHandler: if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists self.main_window.btwin.search_entry.hide() + + # Disable all actions, then re-enable a few + for action in Actions.get_all_actions(): action.set_sensitive(False) #set all actions disabled for action in ( - Actions.TOGGLE_REPORTS_WINDOW, - Actions.TOGGLE_BLOCKS_WINDOW, - Actions.TOGGLE_AUTO_HIDE_PORT_LABELS, - Actions.TOGGLE_SCROLL_LOCK, - Actions.TOGGLE_SNAP_TO_GRID, + Actions.APPLICATION_QUIT, Actions.FLOW_GRAPH_NEW, + Actions.FLOW_GRAPH_OPEN, Actions.FLOW_GRAPH_SAVE_AS, + Actions.FLOW_GRAPH_CLOSE, Actions.ABOUT_WINDOW_DISPLAY, + Actions.FLOW_GRAPH_SCREEN_CAPTURE, Actions.HELP_WINDOW_DISPLAY, + Actions.TYPES_WINDOW_DISPLAY, Actions.TOGGLE_BLOCKS_WINDOW, + Actions.TOGGLE_REPORTS_WINDOW, Actions.TOGGLE_HIDE_DISABLED_BLOCKS, + Actions.TOOLS_RUN_FDESIGN, Actions.TOGGLE_SCROLL_LOCK, + Actions.CLEAR_REPORTS, Actions.SAVE_REPORTS, + Actions.TOGGLE_AUTO_HIDE_PORT_LABELS, Actions.TOGGLE_SNAP_TO_GRID, Actions.TOGGLE_SHOW_BLOCK_COMMENTS, Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB, - ): action.load_from_preferences() + Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY, + Actions.FLOW_GRAPH_OPEN_QSS_THEME, + ): + action.set_sensitive(True) + if hasattr(action, 'load_from_preferences'): + action.load_from_preferences() + elif action == Actions.APPLICATION_QUIT: if self.main_window.close_pages(): gtk.main_quit() @@ -416,6 +415,10 @@ class ActionHandler: action.save_to_preferences() elif action == Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB: action.save_to_preferences() + elif action == Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY: + action.save_to_preferences() + for page in self.main_window.get_pages(): + page.get_flow_graph().update() ################################################## # Param Modifications ################################################## @@ -465,11 +468,23 @@ class ActionHandler: ################################################## elif action == Actions.FLOW_GRAPH_NEW: self.main_window.new_page() + if args: + self.get_flow_graph()._options_block.get_param('generate_options').set_value(args[0]) + self.get_flow_graph().update() elif action == Actions.FLOW_GRAPH_OPEN: file_paths = OpenFlowGraphFileDialog(self.get_page().get_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): self.main_window.new_page(file_path, show=(i==0)) + elif action == Actions.FLOW_GRAPH_OPEN_QSS_THEME: + file_paths = OpenQSSFileDialog(GR_PREFIX + '/share/gnuradio/themes/').run() + if file_paths: + try: + from gnuradio import gr + gr.prefs().set_string("qtgui", "qss", file_paths[0]) + gr.prefs().save() + except Exception as e: + Messages.send("Failed to save QSS preference: " + str(e)) elif action == Actions.FLOW_GRAPH_CLOSE: self.main_window.close_page() elif action == Actions.FLOW_GRAPH_SAVE: diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index ce1f2cf873..20929344c0 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -31,6 +31,8 @@ NO_MODS_MASK = 0 _actions_keypress_dict = dict() _keymap = gtk.gdk.keymap_get_default() _used_mods_mask = NO_MODS_MASK + + def handle_key_press(event): """ Call the action associated with the key press event. @@ -43,21 +45,30 @@ def handle_key_press(event): 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 + # extract the key value and the consumed modifiers keyval, egroup, level, consumed = _keymap.translate_keyboard_state( event.hardware_keycode, event.state, event.group) - #get the modifier mask and ignore irrelevant modifiers + # get the modifier mask and ignore irrelevant modifiers mod_mask = event.state & ~consumed & _used_mods_mask - #look up the keypress and call the action - try: _actions_keypress_dict[(keyval, mod_mask)]() - except KeyError: return False #not handled - return True #handled here + # look up the keypress and call the action + try: + _actions_keypress_dict[(keyval, mod_mask)]() + except KeyError: + return False # not handled + else: + return True # handled here _all_actions_list = list() -def get_all_actions(): return _all_actions_list + + +def get_all_actions(): + return _all_actions_list _accel_group = gtk.AccelGroup() -def get_accel_group(): return _accel_group + + +def get_accel_group(): + return _accel_group class _ActionBase(object): @@ -69,14 +80,15 @@ class _ActionBase(object): _all_actions_list.append(self) for i in range(len(keypresses)/2): keyval, mod_mask = keypresses[i*2:(i+1)*2] - #register this keypress + # register this keypress if _actions_keypress_dict.has_key((keyval, mod_mask)): - raise KeyError('keyval/mod_mask pair already registered "%s"'%str((keyval, mod_mask))) + raise KeyError('keyval/mod_mask pair already registered "%s"' % str((keyval, mod_mask))) _actions_keypress_dict[(keyval, mod_mask)] = self - #set the accelerator group, and accelerator path - #register the key name and mod mask with the accelerator path - if label is None: continue #dont register accel - accel_path = '<main>/'+self.get_name() + # set the accelerator group, and accelerator path + # register the key name and mod mask with the accelerator path + if label is None: + continue # dont register accel + 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) @@ -86,10 +98,10 @@ 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. """ - try: - import Actions - return filter(lambda attr: getattr(Actions, attr) == self, dir(Actions))[0] - except: return self.get_name() + for name, value in globals(): + if value == self: + return value + return self.get_name() def __repr__(self): return str(self) @@ -115,12 +127,10 @@ class Action(gtk.Action, _ActionBase): key_presses: a tuple of (keyval1, mod_mask1, keyval2, mod_mask2, ...) the: regular gtk.Action parameters (defaults to None) """ - if name is None: name = label - gtk.Action.__init__(self, - name=name, label=label, - tooltip=tooltip, stock_id=stock_id, - ) - #register this action + if name is None: + name = label + gtk.Action.__init__(self, name=name, label=label, tooltip=tooltip, + stock_id=stock_id) _ActionBase.__init__(self, label, keypresses) @@ -141,9 +151,8 @@ class ToggleAction(gtk.ToggleAction, _ActionBase): """ if name is None: name = label - gtk.ToggleAction.__init__(self, - name=name, label=label, tooltip=tooltip, stock_id=stock_id, - ) + gtk.ToggleAction.__init__(self, name=name, label=label, + tooltip=tooltip, stock_id=stock_id) _ActionBase.__init__(self, label, keypresses) self.preference_name = preference_name self.default = default @@ -284,6 +293,12 @@ TOGGLE_SHOW_CODE_PREVIEW_TAB = ToggleAction( preference_name='show_generated_code_tab', default=False, ) +TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY = ToggleAction( + label='Show Flowgraph Complexity', + tooltip="How many Balints is the flowgraph...", + preference_name='show_flowgraph_complexity', + default=False, +) BLOCK_CREATE_HIER = Action( label='C_reate Hier', tooltip='Create hier block from selected blocks', @@ -424,6 +439,11 @@ XML_PARSER_ERRORS_DISPLAY = Action( tooltip='View errors that occured while parsing XML files', 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, +) TOOLS_RUN_FDESIGN = Action( label='Filter Design Tool', tooltip='Execute gr_filter_design', diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py index f0f8dac7fb..2ab5b2a712 100644 --- a/grc/gui/Bars.py +++ b/grc/gui/Bars.py @@ -17,12 +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 Actions import pygtk pygtk.require('2.0') import gtk -##The list of actions for the toolbar. +from . import Actions + + +# The list of actions for the toolbar. TOOLBAR_LIST = ( Actions.FLOW_GRAPH_NEW, Actions.FLOW_GRAPH_OPEN, @@ -57,11 +59,10 @@ TOOLBAR_LIST = ( Actions.OPEN_HIER, ) -##The list of actions and categories for the menu bar. - +# The list of actions and categories for the menu bar. MENU_BAR_LIST = ( (gtk.Action('File', '_File', None, None), [ - Actions.FLOW_GRAPH_NEW, + 'flow_graph_new', Actions.FLOW_GRAPH_OPEN, None, Actions.FLOW_GRAPH_SAVE, @@ -115,6 +116,9 @@ MENU_BAR_LIST = ( ]), (gtk.Action('Tools', '_Tools', None, None), [ Actions.TOOLS_RUN_FDESIGN, + Actions.FLOW_GRAPH_OPEN_QSS_THEME, + None, + Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY, None, Actions.TOOLS_MORE_TO_COME, ]), @@ -127,7 +131,7 @@ MENU_BAR_LIST = ( ]), ) - +# The list of actions for the context menu. CONTEXT_MENU_LIST = [ Actions.BLOCK_CUT, Actions.BLOCK_COPY, @@ -162,17 +166,46 @@ class Toolbar(gtk.Toolbar): gtk.Toolbar.__init__(self) self.set_style(gtk.TOOLBAR_ICONS) for action in TOOLBAR_LIST: - if action: #add a tool item - self.add(action.create_tool_item()) - #this reset of the tooltip property is required (after creating the tool item) for the tooltip to show + if action: # add a tool item + item = action.create_tool_item() + # this reset of the tooltip property is required + # (after creating the tool item) for the tooltip to show action.set_property('tooltip', action.get_property('tooltip')) - else: self.add(gtk.SeparatorToolItem()) + else: + item = gtk.SeparatorToolItem() + self.add(item) + + +class MenuHelperMixin(object): + """Mixin class to help build menus from the above action lists""" + + def _fill_menu(self, actions, menu=None): + """Create a menu from list of actions""" + 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() + else: + menu_item = item.create_menu_item() + menu.append(menu_item) + menu.show_all() + return menu + def _make_sub_menu(self, main, actions): + """Create a submenu from a main action and a list of actions""" + main = main.create_menu_item() + main.set_submenu(self._fill_menu(actions)) + return main -class MenuBar(gtk.MenuBar): + +class MenuBar(gtk.MenuBar, MenuHelperMixin): """The gtk menu bar with actions added from the menu bar list.""" - def __init__(self): + def __init__(self, generate_modes, action_handler_callback): """ Parse the list of submenus from the menubar list. For each submenu, get a list of action names. @@ -180,37 +213,37 @@ class MenuBar(gtk.MenuBar): Add the submenu to the menu bar. """ gtk.MenuBar.__init__(self) + self.generate_modes = generate_modes + self.action_handler_callback = action_handler_callback for main_action, actions in MENU_BAR_LIST: - #create the main menu item - main_menu_item = main_action.create_menu_item() - self.append(main_menu_item) - #create the menu - main_menu = gtk.Menu() - main_menu_item.set_submenu(main_menu) - for action in actions: - main_menu.append(action.create_menu_item() if action else - gtk.SeparatorMenuItem()) - main_menu.show_all() #this show all is required for the separators to show - - -class ContextMenu(gtk.Menu): + self.append(self._make_sub_menu(main_action, actions)) + + def create_flow_graph_new(self): + """Sub menu to create flow-graph with pre-set generate mode""" + + def callback_adaptor(item, key): + """Sets original FLOW_GRAPH_NEW action as source""" + self.action_handler_callback(Actions.FLOW_GRAPH_NEW, key) + + sub_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) + item.connect('activate', callback_adaptor, key) + sub_menu.append(item) + sub_menu.show_all() + main = gtk.ImageMenuItem(gtk.STOCK_NEW) + main.set_label(Actions.FLOW_GRAPH_NEW.get_label()) + main.set_submenu(sub_menu) + return main + + +class ContextMenu(gtk.Menu, MenuHelperMixin): """The gtk menu with actions added from the context menu list.""" def __init__(self): gtk.Menu.__init__(self) - for action in CONTEXT_MENU_LIST: - if isinstance(action, tuple): - action, sub_menu_action_list = action - item = action.create_menu_item() - self.append(item) - sub_menu = gtk.Menu() - item.set_submenu(sub_menu) - for action in sub_menu_action_list: - sub_menu.append(action.create_menu_item() if action else - gtk.SeparatorMenuItem()) - sub_menu.show_all() - - else: - self.append(action.create_menu_item() if action else - gtk.SeparatorMenuItem()) - self.show_all() + self._fill_menu(CONTEXT_MENU_LIST, self) diff --git a/grc/gui/Block.py b/grc/gui/Block.py index 11273a537b..7350e4bdfe 100644 --- a/grc/gui/Block.py +++ b/grc/gui/Block.py @@ -21,6 +21,7 @@ from Element import Element import Utils import Colors from .. base import odict +from .. python.Param import num_to_str from Constants import BORDER_PROXIMITY_SENSITIVITY from Constants import ( BLOCK_LABEL_PADDING, PORT_SPACING, PORT_SEPARATION, LABEL_SEPARATION, @@ -35,9 +36,17 @@ import pango 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>""" -COMMENT_MARKUP_TMPL="""\ + +# Includes the additional complexity markup if enabled +COMMENT_COMPLEXITY_MARKUP_TMPL="""\ #set $foreground = $block.get_enabled() and '#444' or '#888' -<span foreground="$foreground" font_desc="$font">$encode($block.get_comment())</span>""" +#if $complexity +<span foreground="#444" size="medium" font_desc="$font"><b>$encode($complexity)</b></span> +#end if +#if $comment +<span foreground="$foreground" font_desc="$font">$encode($comment)</span>#slurp +#end if""" + class Block(Element): """The graphical signal block.""" @@ -218,16 +227,31 @@ class Block(Element): self.create_comment_label() def create_comment_label(self): - comment = self.get_comment() - if comment: - layout = gtk.DrawingArea().create_pango_layout('') - layout.set_markup(Utils.parse_template(COMMENT_MARKUP_TMPL, block=self, font=BLOCK_FONT)) - width, height = layout.get_pixel_size() - pixmap = self.get_parent().new_pixmap(width, height) + comment = self.get_comment() # Returns None if there are no comments + complexity = None + + # Show the flowgraph complexity on the top block if enabled + if Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY.get_active() and self.get_key() == "options": + complexity = "Complexity: {}bal".format(num_to_str(self.get_parent().get_complexity())) + + layout = gtk.DrawingArea().create_pango_layout('') + layout.set_markup(Utils.parse_template(COMMENT_COMPLEXITY_MARKUP_TMPL, + block=self, + comment=comment, + complexity=complexity, + font=BLOCK_FONT)) + + # Setup the pixel map. Make sure that layout not empty + width, height = layout.get_pixel_size() + if width and height: + padding = BLOCK_LABEL_PADDING + pixmap = self.get_parent().new_pixmap(width + 2 * padding, + height + 2 * padding) gc = pixmap.new_gc() gc.set_foreground(Colors.COMMENT_BACKGROUND_COLOR) - pixmap.draw_rectangle(gc, True, 0, 0, width, height) - pixmap.draw_layout(gc, 0, 0, layout) + pixmap.draw_rectangle( + gc, True, 0, 0, width + 2 * padding, height + 2 * padding) + pixmap.draw_layout(gc, padding, padding, layout) self._comment_pixmap = pixmap else: self._comment_pixmap = None diff --git a/grc/gui/Constants.py b/grc/gui/Constants.py index a8395f631e..980396f85d 100644 --- a/grc/gui/Constants.py +++ b/grc/gui/Constants.py @@ -26,7 +26,7 @@ import sys from gnuradio import gr _gr_prefs = gr.prefs() - +GR_PREFIX = gr.prefix() # default path for the open/save dialogs DEFAULT_FILE_PATH = os.getcwd() @@ -118,4 +118,3 @@ SCROLL_DISTANCE = 15 # How close the mouse click can be to a line and register a connection select. LINE_SELECT_SENSITIVITY = 5 - diff --git a/grc/gui/FileDialogs.py b/grc/gui/FileDialogs.py index 96cbd94f20..730ac6fba0 100644 --- a/grc/gui/FileDialogs.py +++ b/grc/gui/FileDialogs.py @@ -35,6 +35,7 @@ OPEN_FLOW_GRAPH = 'open flow graph' SAVE_FLOW_GRAPH = 'save flow graph' SAVE_REPORTS = 'save reports' 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?""" @@ -72,6 +73,13 @@ def get_all_files_filter(): filter.add_pattern('*') return filter +##the filter for qss files +def get_qss_themes_filter(): + filter = gtk.FileFilter() + filter.set_name('QSS Themes') + filter.add_pattern('*.qss') + return filter + ################################################## # File Dialogs ################################################## @@ -126,6 +134,10 @@ class FileDialog(FileDialogHelper): 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...') + 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 def add_and_set_filter(self, filter): @@ -171,7 +183,7 @@ class FileDialog(FileDialogHelper): ############################################# # Handle Open Dialogs ############################################# - elif self.type in (OPEN_FLOW_GRAPH,): + elif self.type in (OPEN_FLOW_GRAPH, OPEN_QSS_THEME): filenames = self.get_filenames() for filename in filenames: if not path.exists(filename): #show a warning and re-run @@ -195,5 +207,6 @@ class FileDialog(FileDialogHelper): class OpenFlowGraphFileDialog(FileDialog): type = OPEN_FLOW_GRAPH class SaveFlowGraphFileDialog(FileDialog): type = SAVE_FLOW_GRAPH +class OpenQSSFileDialog(FileDialog): type = OPEN_QSS_THEME class SaveReportsFileDialog(FileDialog): type = SAVE_REPORTS class SaveImageFileDialog(FileDialog): type = SAVE_IMAGE diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index b1e88aae8e..fc6a711572 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -285,10 +285,12 @@ class FlowGraph(Element): 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() #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.iter_blocks(): diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index 07d0661e94..f658a85062 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -65,12 +65,17 @@ PAGE_TITLE_MARKUP_TMPL = """\ class MainWindow(gtk.Window): """The topmost window with menus, the tool bar, and other major windows.""" - def __init__(self, platform): + def __init__(self, platform, action_handler_callback): """ MainWindow contructor Setup the menu, toolbar, flowgraph editor notebook, block selection window... """ self._platform = platform + gen_opts = platform.get_block('options').get_param('generate_options') + generate_mode_default = gen_opts.get_value() + generate_modes = [ + (o.get_key(), o.get_name(), o.get_key() == generate_mode_default) + for o in gen_opts.get_options()] #setup window gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) vbox = gtk.VBox() @@ -78,7 +83,8 @@ class MainWindow(gtk.Window): self.add(vbox) #create the menu bar and toolbar self.add_accel_group(Actions.get_accel_group()) - vbox.pack_start(Bars.MenuBar(), False) + menu_bar = Bars.MenuBar(generate_modes, action_handler_callback) + vbox.pack_start(menu_bar, False) vbox.pack_start(Bars.Toolbar(), False) vbox.pack_start(self.hpaned) #create the notebook @@ -177,7 +183,7 @@ class MainWindow(gtk.Window): try: #try to load from file if file_path: Messages.send_start_load(file_path) flow_graph = self._platform.get_new_flow_graph() - flow_graph.grc_file_path = file_path; + flow_graph.grc_file_path = file_path #print flow_graph page = NotebookPage( self, diff --git a/grc/gui/Param.py b/grc/gui/Param.py index b9436ab06e..ca0a8c60e5 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -19,7 +19,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA import Utils from Element import Element -from . Constants import PARAM_FONT +from . Constants import PARAM_FONT, GR_PREFIX import pygtk pygtk.require('2.0') import gtk @@ -233,7 +233,14 @@ class FileParam(EntryParam): #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, '') - if not os.path.exists(dirname): dirname = os.getcwd() #fix bad paths + # 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): + dirname = os.path.join(GR_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, diff --git a/grc/python/Constants.py b/grc/python/Constants.py index 02be22a441..b7a370cad7 100644 --- a/grc/python/Constants.py +++ b/grc/python/Constants.py @@ -19,6 +19,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA import os from os.path import expanduser +import numpy import stat from gnuradio import gr @@ -53,6 +54,16 @@ FLOW_GRAPH_TEMPLATE = os.path.join(DATA_DIR, 'flow_graph.tmpl') BLOCK_DTD = os.path.join(DATA_DIR, 'block.dtd') DEFAULT_FLOW_GRAPH = os.path.join(DATA_DIR, 'default_flow_graph.grc') +#define types, native python + numpy +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, + 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 diff --git a/grc/python/Generator.py b/grc/python/Generator.py index 3c687a2d64..d48be2f00e 100644 --- a/grc/python/Generator.py +++ b/grc/python/Generator.py @@ -21,6 +21,7 @@ import os import sys import subprocess import tempfile +import shlex from distutils.spawn import find_executable from Cheetah.Template import Template @@ -120,20 +121,13 @@ class TopBlockGenerator(object): Returns: a popen object """ - # extract the path to the python executable - python_exe = sys.executable - - # when using wx gui on mac os, execute with pythonw - # using pythonw is not necessary anymore, disabled below - # if self._generate_options == 'wx_gui' and 'darwin' in sys.platform.lower(): - # python_exe = 'pythonw' - def args_to_string(args): """Accounts for spaces in args""" return ' '.join(repr(arg) if ' ' in arg else arg for arg in args) - # setup the command args to run - cmds = [python_exe, '-u', self.get_file_path()] # -u is unbuffered stdio + run_command = self._flow_graph.get_option('run_command') + cmds = shlex.split(run_command.format(python=sys.executable, + filename=self.get_file_path())) # when in no gui mode on linux, use a graphical terminal (looks nice) xterm_executable = find_executable(XTERM_EXECUTABLE) diff --git a/grc/python/Param.py b/grc/python/Param.py index 0e72fcbfb2..27e5b76320 100644 --- a/grc/python/Param.py +++ b/grc/python/Param.py @@ -19,8 +19,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from .. base.Param import Param as _Param from .. gui.Param import Param as _GUIParam + import Constants -import numpy +from Constants import VECTOR_TYPES, COMPLEX_TYPES, REAL_TYPES, INT_TYPES + from gnuradio import eng_notation import re from gnuradio import gr @@ -33,16 +35,19 @@ _show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$') import __builtin__ ID_BLACKLIST = ['self', 'options', 'gr', 'blks2', 'wxgui', 'wx', 'math', 'forms', 'firdes'] + \ filter(lambda x: not x.startswith('_'), dir(gr.top_block())) + dir(__builtin__) -#define types, native python + numpy -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, - 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) + + +def num_to_str(num): + """ Display logic for numbers """ + if isinstance(num, COMPLEX_TYPES): + num = complex(num) #cast to python complex + if num == 0: return '0' #value is zero + elif num.imag == 0: return '%s'%eng_notation.num_to_str(num.real) #value is real + elif num.real == 0: return '%sj'%eng_notation.num_to_str(num.imag) #value is imaginary + elif num.imag < 0: return '%s-%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(abs(num.imag))) + else: return '%s+%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(num.imag)) + else: return str(num) + class Param(_Param, _GUIParam): @@ -88,18 +93,7 @@ class Param(_Param, _GUIParam): ################################################## if not self.is_valid(): return _truncate(self.get_value()) if self.get_value() in self.get_option_keys(): return self.get_option(self.get_value()).get_name() - ################################################## - # display logic for numbers - ################################################## - def num_to_str(num): - if isinstance(num, COMPLEX_TYPES): - num = complex(num) #cast to python complex - if num == 0: return '0' #value is zero - elif num.imag == 0: return '%s'%eng_notation.num_to_str(num.real) #value is real - elif num.real == 0: return '%sj'%eng_notation.num_to_str(num.imag) #value is imaginary - elif num.imag < 0: return '%s-%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(abs(num.imag))) - else: return '%s+%sj'%(eng_notation.num_to_str(num.real), eng_notation.num_to_str(num.imag)) - else: return str(num) + ################################################## # split up formatting by type ################################################## diff --git a/grc/python/flow_graph.tmpl b/grc/python/flow_graph.tmpl index 3cc16e7e14..72891b7af8 100644 --- a/grc/python/flow_graph.tmpl +++ b/grc/python/flow_graph.tmpl @@ -7,7 +7,7 @@ ##@param imports the import statements ##@param flow_graph the flow_graph ##@param variables the variable blocks -##@param parameters the paramater blocks +##@param parameters the parameter blocks ##@param blocks the signal blocks ##@param connections the connections ##@param msgs the msg type connections @@ -53,6 +53,9 @@ if __name__ == '__main__': ######################################################## ##Create Imports ######################################################## +#if $flow_graph.get_option('qt_qss_theme') +#set imports = $sorted(set($imports + ["import os", "import sys"])) +#end if #if any(imp.endswith("# grc-generated hier_block") for imp in $imports) import os import sys @@ -95,9 +98,9 @@ class $(class_name)(gr.top_block, Qt.QWidget): Qt.QWidget.__init__(self) self.setWindowTitle("$title") try: - self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc')) + self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc')) except: - pass + pass self.top_scroll_layout = Qt.QVBoxLayout() self.setLayout(self.top_scroll_layout) self.top_scroll = Qt.QScrollArea() @@ -258,22 +261,34 @@ gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))]) #include source=$connection_templates[($source.get_domain(), $sink.get_domain())] #end for - ######################################################## ## QT sink close method reimplementation ######################################################## #if $generate_options == 'qt_gui' + def closeEvent(self, event): self.settings = Qt.QSettings("GNU Radio", "$class_name") self.settings.setValue("geometry", self.saveGeometry()) event.accept() -#end if + #if $flow_graph.get_option('qt_qss_theme') + def setStyleSheetFromFile(self, filename): + try: + if not os.path.exists(filename): + filename = os.path.join( + gr.prefix(), "share", "gnuradio", "themes", filename) + with open(filename) as ss: + tb.setStyleSheet('1' + ss.read()) + except Exception as e: + print >> sys.stderr, e + #end if +#end if ######################################################## ##Create Callbacks ## Write a set method for this variable that calls the callbacks ######################################################## #for $var in $parameters + $variables + #set $id = $var.get_id() def get_$(id)(self): return self.$id @@ -291,7 +306,6 @@ gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))]) $indent($callback) #end for #end if - #end for ######################################################## ##Create Main @@ -313,25 +327,37 @@ $param.get_make()#slurp $short_id#slurp #end def #if not $generate_options.startswith('hb') +#set $params_eq_list = list() +#if $parameters -if __name__ == '__main__': + +def argument_parser(): parser = OptionParser(option_class=eng_option, usage="%prog: [options]") - #set $params_eq_list = list() #for $param in $parameters #set $type = $param.get_param('type').get_value() #if $type #silent $params_eq_list.append('%s=options.%s'%($param.get_id(), $param.get_id())) - parser.add_option("$make_short_id($param)", "--$param.get_id().replace('_', '-')", dest="$param.get_id()", type="$type", default=$make_default($type, $param), + parser.add_option( + "$make_short_id($param)", "--$param.get_id().replace('_', '-')", dest="$param.get_id()", type="$type", default=$make_default($type, $param), help="Set $($param.get_param('label').get_evaluated() or $param.get_id()) [default=%default]") #end if #end for - (options, args) = parser.parse_args() + return parser +#end if + + +def main(top_block_cls=$(class_name), options=None): + #if $parameters + if options is None: + options, _ = argument_parser().parse_args() + #end if #if $flow_graph.get_option('realtime_scheduling') if gr.enable_realtime_scheduling() != gr.RT_OK: - print "Error: failed to enable realtime scheduling." + print "Error: failed to enable real-time scheduling." #end if + #if $generate_options == 'wx_gui' - tb = $(class_name)($(', '.join($params_eq_list))) + tb = top_block_cls($(', '.join($params_eq_list))) #if $flow_graph.get_option('max_nouts') tb.Run($flow_graph.get_option('run'), $flow_graph.get_option('max_nouts')) #else @@ -344,9 +370,11 @@ if __name__ == '__main__': #elif $generate_options == 'qt_gui' from distutils.version import StrictVersion if StrictVersion(Qt.qVersion()) >= StrictVersion("4.5.0"): - Qt.QApplication.setGraphicsSystem(gr.prefs().get_string('qtgui','style','raster')) + style = gr.prefs().get_string('qtgui', 'style', 'raster') + Qt.QApplication.setGraphicsSystem(style) qapp = Qt.QApplication(sys.argv) - tb = $(class_name)($(', '.join($params_eq_list))) + + tb = top_block_cls($(', '.join($params_eq_list))) #if $flow_graph.get_option('run') #if $flow_graph.get_option('max_nouts') tb.start($flow_graph.get_option('max_nouts')) @@ -354,6 +382,9 @@ if __name__ == '__main__': tb.start() #end if #end if + #if $flow_graph.get_option('qt_qss_theme') + tb.setStyleSheetFromFile($repr($flow_graph.get_option('qt_qss_theme'))) + #end if tb.show() def quitting(): @@ -368,9 +399,8 @@ if __name__ == '__main__': sys.stderr.write("Monitor '{0}' does not have an enable ('en') parameter.".format("tb.$m.get_id()")) #end for qapp.exec_() - tb = None # to clean up Qt widgets #elif $generate_options == 'no_gui' - tb = $(class_name)($(', '.join($params_eq_list))) + tb = top_block_cls($(', '.join($params_eq_list))) #set $run_options = $flow_graph.get_option('run_options') #if $run_options == 'prompt' #if $flow_graph.get_option('max_nouts') @@ -398,4 +428,8 @@ if __name__ == '__main__': #end for tb.wait() #end if + + +if __name__ == '__main__': + main() #end if |