diff options
39 files changed, 1360 insertions, 395 deletions
diff --git a/gr-digital/grc/blks2_packet_decoder.xml b/gr-digital/grc/blks2_packet_decoder.xml index c3672450ea..3b66f0024f 100644 --- a/gr-digital/grc/blks2_packet_decoder.xml +++ b/gr-digital/grc/blks2_packet_decoder.xml @@ -8,6 +8,7 @@ <name>Packet Decoder</name> <key>blks2_packet_decoder</key> <category>Deprecated</category> + <flags>deprecated</flags> <import>from grc_gnuradio import blks2 as grc_blks2</import> <make>grc_blks2.packet_demod_$(type.fcn)(grc_blks2.packet_decoder( access_code=$access_code, diff --git a/gr-digital/grc/blks2_packet_encoder.xml b/gr-digital/grc/blks2_packet_encoder.xml index 92de9c9570..75cb5eabf1 100644 --- a/gr-digital/grc/blks2_packet_encoder.xml +++ b/gr-digital/grc/blks2_packet_encoder.xml @@ -8,6 +8,7 @@ <name>Packet Encoder</name> <key>blks2_packet_encoder</key> <category>Deprecated</category> + <flags>deprecated</flags> <import>from grc_gnuradio import blks2 as grc_blks2</import> <make>grc_blks2.packet_mod_$(type.fcn)(grc_blks2.packet_encoder( samples_per_symbol=$samples_per_symbol, diff --git a/gr-digital/include/gnuradio/digital/ofdm_equalizer_base.h b/gr-digital/include/gnuradio/digital/ofdm_equalizer_base.h index ca53382944..85443da8f2 100644 --- a/gr-digital/include/gnuradio/digital/ofdm_equalizer_base.h +++ b/gr-digital/include/gnuradio/digital/ofdm_equalizer_base.h @@ -30,7 +30,8 @@ namespace gr { namespace digital { - /* \brief Base class for implementation details of frequency-domain OFDM equalizers. + /*! + * \brief Base class for implementation details of frequency-domain OFDM equalizers. * \ingroup ofdm_blk * \ingroup equalizers_blk */ diff --git a/gr-digital/include/gnuradio/digital/ofdm_equalizer_simpledfe.h b/gr-digital/include/gnuradio/digital/ofdm_equalizer_simpledfe.h index d526f9f157..03a4c82b91 100644 --- a/gr-digital/include/gnuradio/digital/ofdm_equalizer_simpledfe.h +++ b/gr-digital/include/gnuradio/digital/ofdm_equalizer_simpledfe.h @@ -29,7 +29,8 @@ namespace gr { namespace digital { - /* \brief Simple decision feedback equalizer for OFDM. + /*! + * \brief Simple decision feedback equalizer for OFDM. * \ingroup ofdm_blk * \ingroup equalizers_blk * diff --git a/gr-digital/include/gnuradio/digital/ofdm_equalizer_static.h b/gr-digital/include/gnuradio/digital/ofdm_equalizer_static.h index 892025c2b0..ed3fe83f04 100644 --- a/gr-digital/include/gnuradio/digital/ofdm_equalizer_static.h +++ b/gr-digital/include/gnuradio/digital/ofdm_equalizer_static.h @@ -29,7 +29,8 @@ namespace gr { namespace digital { - /* \brief Very simple static equalizer for OFDM. + /*! + * \brief Very simple static equalizer for OFDM. * \ingroup ofdm_blk * \ingroup equalizers_blk * diff --git a/gr-uhd/grc/gen_uhd_usrp_blocks.py b/gr-uhd/grc/gen_uhd_usrp_blocks.py index fd2e77707e..e99de0d7d0 100644 --- a/gr-uhd/grc/gen_uhd_usrp_blocks.py +++ b/gr-uhd/grc/gen_uhd_usrp_blocks.py @@ -82,6 +82,12 @@ self.\$(id).set_antenna(\$ant$(n), $n) self.\$(id).set_bandwidth(\$bw$(n), $n) \#end if #if $sourk == 'source' + \#if \$lo_export$(n)() and not \$hide_lo_controls() +self.\$(id).set_lo_export_enabled(\$lo_export$(n), uhd.ALL_LOS, $n) + \#end if + \#if \$lo_source$(n)() and not \$hide_lo_controls() +self.\$(id).set_lo_source(\$lo_source$(n), uhd.ALL_LOS, $n) + \#end if \#if \$dc_offs_enb$(n)() self.\$(id).set_auto_dc_offset(\$dc_offs_enb$(n), $n) \#end if @@ -101,6 +107,14 @@ self.\$(id).set_normalized_gain(\$gain$(n), $n) self.\$(id).set_gain(\$gain$(n), $n) \#end if </callback> + <callback>\#if not \$hide_lo_controls() +set_lo_source(\$lo_source$(n), uhd.ALL_LOS, $n) +\#end if + </callback> + <callback>\#if not \$hide_lo_controls() +set_lo_export_enabled(\$lo_export$(n), uhd.ALL_LOS, $n) +\#end if + </callback> <callback>set_antenna(\$ant$(n), $n)</callback> <callback>set_bandwidth(\$bw$(n), $n)</callback> #end for @@ -524,6 +538,62 @@ PARAMS_TMPL = """ <param> </param> #if $sourk == 'source' <param> + <name>Ch$(n): LO Source</name> + <key>lo_source$(n)</key> + <value>internal</value> + <type>string</type> + <hide> + \#if not \$nchan() > $n + all + \#elif \$hide_lo_controls() + all + \#else + none + \#end if + </hide> + <option> + <name>Internal</name> + <key>internal</key> + </option> + <option> + <name>External</name> + <key>external</key> + </option> + <option> + <name>Companion</name> + <key>companion</key> + </option> + <tab>RF Options</tab> + </param> +#end if +#if $sourk == 'source' + <param> + <name>Ch$(n): LO Export</name> + <key>lo_export$(n)</key> + <value>False</value> + <type>bool</type> + <hide> + \#if not \$nchan() > $n + all + \#elif \$hide_lo_controls() + all + \#else + none + \#end if + </hide> + <option> + <name>True</name> + <key>True</key> + </option> + <option> + <name>False</name> + <key>False</key> + </option> + <tab>RF Options</tab> + </param> +#end if +#if $sourk == 'source' + <param> <name>Ch$(n): Enable DC Offset Correction</name> <key>dc_offs_enb$(n)</key> <value>""</value> @@ -573,6 +643,25 @@ SHOW_CMD_PORT_PARAM = """ </param> """ +SHOW_LO_CONTROLS_PARAM = """ + <param> + <name>Show LO Controls</name> + <key>hide_lo_controls</key> + <value>True</value> + <type>bool</type> + <hide>part</hide> + <option> + <name>Yes</name> + <key>False</key> + </option> + <option> + <name>No</name> + <key>True</key> + </option> + <tab>Advanced</tab> + </param> +""" + TSBTAG_PARAM = """ <param> <name>TSB tag name</name> <key>len_tag_name</key> @@ -605,7 +694,8 @@ if __name__ == '__main__': else: raise Exception, 'is %s a source or sink?'%file params = ''.join([parse_tmpl(PARAMS_TMPL, n=n, sourk=sourk) for n in range(max_num_channels)]) - params += SHOW_CMD_PORT_PARAM + params += SHOW_CMD_PORT_PARAM + params += SHOW_LO_CONTROLS_PARAM if sourk == 'sink': params += TSBTAG_PARAM lentag_arg = TSBTAG_ARG diff --git a/gr-uhd/include/gnuradio/uhd/usrp_source.h b/gr-uhd/include/gnuradio/uhd/usrp_source.h index 19201c031c..a0022b3876 100644 --- a/gr-uhd/include/gnuradio/uhd/usrp_source.h +++ b/gr-uhd/include/gnuradio/uhd/usrp_source.h @@ -140,6 +140,83 @@ namespace gr { */ virtual ::uhd::dict<std::string, std::string> get_usrp_info(size_t chan = 0) = 0; + + /*! + * Get a list of possible LO stage names + * \param chan the channel index 0 to N-1 + * \return a vector of strings for possible LO names + */ + virtual std::vector<std::string> get_lo_names(size_t chan = 0) = 0; + + /*! + * Set the LO source for the usrp device. + * For usrps that support selectable LOs, this function + * allows switching between them. + * Typical options for source: internal, external. + * \param src a string representing the LO source + * \param name the name of the LO stage to update + * \param chan the channel index 0 to N-1 + */ + virtual void set_lo_source(const std::string &src, const std::string &name, size_t chan = 0) = 0; + + /*! + * Get the currently set LO source. + * \param name the name of the LO stage to query + * \param chan the channel index 0 to N-1 + * \return the configured LO source + */ + virtual const std::string get_lo_source(const std::string &name, size_t chan = 0) = 0; + + /*! + * Get a list of possible LO sources. + * \param name the name of the LO stage to query + * \param chan the channel index 0 to N-1 + * \return a vector of strings for possible settings + */ + virtual std::vector<std::string> get_lo_sources(const std::string &name, size_t chan = 0) = 0; + + /*! + * Set whether the LO used by the usrp device is exported + * For usrps that support exportable LOs, this function + * configures if the LO used by chan is exported or not. + * \param enabled if true then export the LO + * \param name the name of the LO stage to update + * \param chan the channel index 0 to N-1 for the source channel + */ + virtual void set_lo_export_enabled(bool enabled, const std::string &name, size_t chan = 0) = 0; + + /*! + * Returns true if the currently selected LO is being exported. + * \param name the name of the LO stage to query + * \param chan the channel index 0 to N-1 + */ + virtual bool get_lo_export_enabled(const std::string &name, size_t chan = 0) = 0; + + /*! + * Set the RX LO frequency (Advanced). + * \param freq the frequency to set the LO to + * \param name the name of the LO stage to update + * \param chan the channel index 0 to N-1 + * \return a coerced LO frequency + */ + virtual double set_lo_freq(double freq, const std::string &name, size_t chan = 0) = 0; + + /*! + * Get the current RX LO frequency (Advanced). + * \param name the name of the LO stage to query + * \param chan the channel index 0 to N-1 + * \return the configured LO frequency + */ + virtual double get_lo_freq(const std::string &name, size_t chan = 0) = 0; + + /*! + * Get the LO frequency range of the RX LO. + * \param name the name of the LO stage to query + * \param chan the channel index 0 to N-1 + * \return a frequency range object + */ + virtual ::uhd::freq_range_t get_lo_freq_range(const std::string &name, size_t chan = 0) = 0; + /*! * Enable/disable the automatic DC offset correction. * The automatic correction subtracts out the long-run average. diff --git a/gr-uhd/lib/usrp_block_impl.h b/gr-uhd/lib/usrp_block_impl.h index cb07fb356d..a1bbf97826 100644 --- a/gr-uhd/lib/usrp_block_impl.h +++ b/gr-uhd/lib/usrp_block_impl.h @@ -40,6 +40,14 @@ namespace gr { namespace uhd { + static const size_t ALL_MBOARDS = ::uhd::usrp::multi_usrp::ALL_MBOARDS; + static const size_t ALL_CHANS = ::uhd::usrp::multi_usrp::ALL_CHANS; + static const std::string ALL_GAINS = ::uhd::usrp::multi_usrp::ALL_GAINS; +#ifdef UHD_USRP_MULTI_USRP_LO_CONFIG_API + static const std::string ALL_LOS = ::uhd::usrp::multi_usrp::ALL_LOS; +#else + static const std::string ALL_LOS; +#endif class usrp_block_impl : virtual public usrp_block { public: diff --git a/gr-uhd/lib/usrp_sink_impl.cc b/gr-uhd/lib/usrp_sink_impl.cc index e2e4d334b6..d8f98fdd80 100644 --- a/gr-uhd/lib/usrp_sink_impl.cc +++ b/gr-uhd/lib/usrp_sink_impl.cc @@ -29,8 +29,6 @@ namespace gr { namespace uhd { - static const size_t ALL_CHANS = ::uhd::usrp::multi_usrp::ALL_CHANS; - usrp_sink::sptr usrp_sink::make(const ::uhd::device_addr_t &device_addr, const ::uhd::io_type_t &io_type, diff --git a/gr-uhd/lib/usrp_source_impl.cc b/gr-uhd/lib/usrp_source_impl.cc index b2bddbef17..ed0b891770 100644 --- a/gr-uhd/lib/usrp_source_impl.cc +++ b/gr-uhd/lib/usrp_source_impl.cc @@ -281,6 +281,104 @@ namespace gr { return _dev->get_rx_bandwidth_range(chan); } + std::vector<std::string> + usrp_source_impl::get_lo_names(size_t chan) + { +#ifdef UHD_USRP_MULTI_USRP_LO_CONFIG_API + chan = _stream_args.channels[chan]; + return _dev->get_rx_lo_names(chan); +#else + throw std::runtime_error("not implemented in this version"); +#endif + } + + const std::string + usrp_source_impl::get_lo_source(const std::string &name, size_t chan) + { +#ifdef UHD_USRP_MULTI_USRP_LO_CONFIG_API + chan = _stream_args.channels[chan]; + return _dev->get_rx_lo_source(name, chan); +#else + throw std::runtime_error("not implemented in this version"); +#endif + } + + std::vector<std::string> + usrp_source_impl::get_lo_sources(const std::string &name, size_t chan) + { +#ifdef UHD_USRP_MULTI_USRP_LO_CONFIG_API + chan = _stream_args.channels[chan]; + return _dev->get_rx_lo_sources(name, chan); +#else + throw std::runtime_error("not implemented in this version"); +#endif + } + + void + usrp_source_impl::set_lo_source(const std::string &src, const std::string &name, size_t chan) + { +#ifdef UHD_USRP_MULTI_USRP_LO_CONFIG_API + chan = _stream_args.channels[chan]; + return _dev->set_rx_lo_source(src, name, chan); +#else + throw std::runtime_error("not implemented in this version"); +#endif + } + + bool + usrp_source_impl::get_lo_export_enabled(const std::string &name, size_t chan) + { +#ifdef UHD_USRP_MULTI_USRP_LO_CONFIG_API + chan = _stream_args.channels[chan]; + return _dev->get_rx_lo_export_enabled(name, chan); +#else + throw std::runtime_error("not implemented in this version"); +#endif + } + + void + usrp_source_impl::set_lo_export_enabled(bool enabled, const std::string &name, size_t chan) + { +#ifdef UHD_USRP_MULTI_USRP_LO_CONFIG_API + chan = _stream_args.channels[chan]; + return _dev->set_rx_lo_export_enabled(enabled, name, chan); +#else + throw std::runtime_error("not implemented in this version"); +#endif + } + + ::uhd::freq_range_t + usrp_source_impl::get_lo_freq_range(const std::string &name, size_t chan) + { +#ifdef UHD_USRP_MULTI_USRP_LO_CONFIG_API + chan = _stream_args.channels[chan]; + return _dev->get_rx_lo_freq_range(name, chan); +#else + throw std::runtime_error("not implemented in this version"); +#endif + } + + double + usrp_source_impl::get_lo_freq(const std::string &name, size_t chan) + { +#ifdef UHD_USRP_MULTI_USRP_LO_CONFIG_API + chan = _stream_args.channels[chan]; + return _dev->get_rx_lo_freq(name, chan); +#else + throw std::runtime_error("not implemented in this version"); +#endif + } + + double + usrp_source_impl::set_lo_freq(double freq, const std::string &name, size_t chan) { +#ifdef UHD_USRP_MULTI_USRP_LO_CONFIG_API + chan = _stream_args.channels[chan]; + return _dev->set_rx_lo_freq(freq, name, chan); +#else + throw std::runtime_error("not implemented in this version"); +#endif + } + void usrp_source_impl::set_auto_dc_offset(const bool enable, size_t chan) { diff --git a/gr-uhd/lib/usrp_source_impl.h b/gr-uhd/lib/usrp_source_impl.h index 78a8341a5a..f6225a7e35 100644 --- a/gr-uhd/lib/usrp_source_impl.h +++ b/gr-uhd/lib/usrp_source_impl.h @@ -77,6 +77,12 @@ namespace gr { ::uhd::sensor_value_t get_sensor(const std::string &name, size_t chan); std::vector<std::string> get_sensor_names(size_t chan); ::uhd::usrp::dboard_iface::sptr get_dboard_iface(size_t chan); + std::vector<std::string> get_lo_names(size_t chan); + const std::string get_lo_source(const std::string &name, size_t chan); + std::vector<std::string> get_lo_sources(const std::string &name, size_t chan); + bool get_lo_export_enabled(const std::string &name, size_t chan); + double get_lo_freq(const std::string &name, size_t chan); + ::uhd::freq_range_t get_lo_freq_range(const std::string &name, size_t chan); // Set Commands void set_subdev_spec(const std::string &spec, size_t mboard); @@ -96,6 +102,9 @@ namespace gr { void set_iq_balance(const std::complex<double> &correction, size_t chan); void set_stream_args(const ::uhd::stream_args_t &stream_args); void set_start_time(const ::uhd::time_spec_t &time); + void set_lo_source(const std::string &src, const std::string &name = ALL_LOS, size_t chan = 0); + void set_lo_export_enabled(bool enabled, const std::string &name = ALL_LOS, size_t chan = 0); + double set_lo_freq(double freq, const std::string &name, size_t chan); void issue_stream_cmd(const ::uhd::stream_cmd_t &cmd); void flush(void); diff --git a/gr-uhd/swig/uhd_swig.i b/gr-uhd/swig/uhd_swig.i index 108f544da3..b82d0fdae9 100644 --- a/gr-uhd/swig/uhd_swig.i +++ b/gr-uhd/swig/uhd_swig.i @@ -155,8 +155,20 @@ static uhd::device_addrs_t find_devices_raw(const uhd::device_addr_t &dev_addr = //////////////////////////////////////////////////////////////////////// %{ static const size_t ALL_MBOARDS = uhd::usrp::multi_usrp::ALL_MBOARDS; +static const size_t ALL_CHANS = uhd::usrp::multi_usrp::ALL_CHANS; +static const std::string ALL_GAINS = uhd::usrp::multi_usrp::ALL_GAINS; + +#ifdef UHD_USRP_MULTI_USRP_LO_CONFIG_API +static const std::string ALL_LOS = uhd::usrp::multi_usrp::ALL_LOS; +#else +static const std::string ALL_LOS; +#endif %} + static const size_t ALL_MBOARDS; +static const size_t ALL_CHANS; +static const std::string ALL_GAINS; +static const std::string ALL_LOS; %{ #include <uhd/version.hpp> diff --git a/gr-utils/python/modtool/gr-newmod/CMakeLists.txt b/gr-utils/python/modtool/gr-newmod/CMakeLists.txt index 6f32f21833..bde9dc0c0f 100644 --- a/gr-utils/python/modtool/gr-newmod/CMakeLists.txt +++ b/gr-utils/python/modtool/gr-newmod/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2011,2012,2014 Free Software Foundation, Inc. +# Copyright 2011,2012,2014,2016 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -40,6 +40,12 @@ set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "") #make sure our local CMake Modules path comes first list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake/Modules) +# Set the version information here +set(VERSION_INFO_MAJOR_VERSION 1) +set(VERSION_INFO_API_COMPAT 0) +set(VERSION_INFO_MINOR_VERSION 0) +set(VERSION_INFO_MAINT_VERSION git) + ######################################################################## # Compiler specific setup ######################################################################## @@ -119,6 +125,7 @@ find_package(Doxygen) set(GR_REQUIRED_COMPONENTS RUNTIME) find_package(Gnuradio "3.7.2" REQUIRED) list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake/Modules) +include(GrVersion) if(NOT CPPUNIT_FOUND) message(FATAL_ERROR "CppUnit required to compile howto") diff --git a/gr-utils/python/modtool/gr-newmod/lib/CMakeLists.txt b/gr-utils/python/modtool/gr-newmod/lib/CMakeLists.txt index 6192c51f04..10a15b7dd9 100644 --- a/gr-utils/python/modtool/gr-newmod/lib/CMakeLists.txt +++ b/gr-utils/python/modtool/gr-newmod/lib/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2011,2012 Free Software Foundation, Inc. +# Copyright 2011,2012,2016 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -47,11 +47,8 @@ endif(APPLE) ######################################################################## # Install built library files ######################################################################## -install(TARGETS gnuradio-howto - LIBRARY DESTINATION lib${LIB_SUFFIX} # .so/.dylib file - ARCHIVE DESTINATION lib${LIB_SUFFIX} # .lib file - RUNTIME DESTINATION bin # .dll file -) +include(GrMiscUtils) +GR_LIBRARY_FOO(gnuradio-howto RUNTIME_COMPONENT "howto_runtime" DEVEL_COMPONENT "howto_devel") ######################################################################## # Build and register unit test @@ -76,3 +73,10 @@ target_link_libraries( ) GR_ADD_TEST(test_howto test-howto) + +######################################################################## +# Print summary +######################################################################## +message(STATUS "Using install prefix: ${CMAKE_INSTALL_PREFIX}") +message(STATUS "Building for version: ${VERSION} / ${LIBVER}") + diff --git a/gr-utils/python/modtool/modtool_add.py b/gr-utils/python/modtool/modtool_add.py index eb269080b4..d560c0878c 100644 --- a/gr-utils/python/modtool/modtool_add.py +++ b/gr-utils/python/modtool/modtool_add.py @@ -82,9 +82,8 @@ class ModToolAdd(ModTool): if self._info['lang'] is None: while self._info['lang'] not in ['cpp', 'python']: self._info['lang'] = raw_input("Language (python/cpp): ") - - if self._info['lang'] == 'c++': - self._info['lang'] = 'cpp' + if self._info['lang'] == 'c++': + self._info['lang'] = 'cpp' print "Language: %s" % {'cpp': 'C++', 'python': 'Python'}[self._info['lang']] diff --git a/grc/blocks/variable_function_probe.xml b/grc/blocks/variable_function_probe.xml index baa996c0ec..47c11b29fe 100644 --- a/grc/blocks/variable_function_probe.xml +++ b/grc/blocks/variable_function_probe.xml @@ -10,7 +10,7 @@ <import>import time</import> <import>import threading</import> <var_make>self.$(id) = $(id) = $value</var_make> - <make>#slurp + <make> def _$(id)_probe(): while True: #set $obj = 'self' + ('.' + $block_id() if $block_id() else '') @@ -22,15 +22,10 @@ def _$(id)_probe(): time.sleep(1.0 / ($poll_rate)) _$(id)_thread = threading.Thread(target=_$(id)_probe) _$(id)_thread.daemon = True -_$(id)_thread.start()</make> +_$(id)_thread.start() + </make> <callback>self.set_$(id)($value)</callback> <param> - <name>Value</name> - <key>value</key> - <value>0</value> - <type>raw</type> - </param> - <param> <name>Block ID</name> <key>block_id</key> <value>my_block_0</value> @@ -55,6 +50,13 @@ _$(id)_thread.start()</make> <value>10</value> <type>real</type> </param> + <param> + <name>Initial Value</name> + <key>value</key> + <value>0</value> + <type>raw</type> + <hide>part</hide> + </param> <doc> Periodically probe a function and set its value to this variable. diff --git a/grc/core/Block.py b/grc/core/Block.py index 106e4bc89a..f67d990857 100644 --- a/grc/core/Block.py +++ b/grc/core/Block.py @@ -27,10 +27,10 @@ from . Constants import ( BLOCK_FLAG_NEED_QT_GUI, BLOCK_FLAG_NEED_WX_GUI, ADVANCED_PARAM_TAB, DEFAULT_PARAM_TAB, BLOCK_FLAG_THROTTLE, BLOCK_FLAG_DISABLE_BYPASS, + BLOCK_FLAG_DEPRECATED, BLOCK_ENABLED, BLOCK_BYPASSED, BLOCK_DISABLED ) from . Element import Element -from . FlowGraph import _variable_matcher def _get_keys(lst): @@ -149,15 +149,16 @@ class Block(Element): # indistinguishable from normal GR blocks. Make explicit # checks for them here since they have no work function or # buffers to manage. - is_virtual_or_pad = self._key in ( + self.is_virtual_or_pad = self._key in ( "virtual_source", "virtual_sink", "pad_source", "pad_sink") - is_variable = self._key.startswith('variable') + self.is_variable = self._key.startswith('variable') + self.is_import = (self._key == 'import') # Disable blocks that are virtual/pads or variables - if is_virtual_or_pad or is_variable: + if self.is_virtual_or_pad or self.is_variable: self._flags += BLOCK_FLAG_DISABLE_BYPASS - if not (is_virtual_or_pad or is_variable or self._key == 'options'): + 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', @@ -168,7 +169,7 @@ class Block(Element): }) )) - if (len(sources) or len(sinks)) and not is_virtual_or_pad: + 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', @@ -178,7 +179,7 @@ class Block(Element): 'tab': ADVANCED_PARAM_TAB }) )) - if len(sources) and not is_virtual_or_pad: + 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', @@ -253,7 +254,7 @@ class Block(Element): self.add_error_message('Check "{}" did not evaluate.'.format(check)) # For variables check the value (only if var_value is used - if _variable_matcher.match(self.get_key()) and self._var_value != '$value': + if self.is_variable and self._var_value != '$value': value = self._var_value try: value = self.get_var_value() @@ -356,7 +357,7 @@ class Block(Element): """ Resolve all import statements. Split each import statement at newlines. - Combine all import statments into a list. + Combine all import statements into a list. Filter empty imports. Returns: @@ -626,6 +627,10 @@ class Block(Element): def bypass_disabled(self): return BLOCK_FLAG_DISABLE_BYPASS in self._flags + @property + def is_deprecated(self): + return BLOCK_FLAG_DEPRECATED in self._flags + ############################################## # Access Params ############################################## diff --git a/grc/core/Constants.py b/grc/core/Constants.py index 1d1a17ee93..462049cc73 100644 --- a/grc/core/Constants.py +++ b/grc/core/Constants.py @@ -47,6 +47,7 @@ BLOCK_FLAG_THROTTLE = 'throttle' BLOCK_FLAG_DISABLE_BYPASS = 'disable_bypass' BLOCK_FLAG_NEED_QT_GUI = 'need_qt_gui' BLOCK_FLAG_NEED_WX_GUI = 'need_wx_gui' +BLOCK_FLAG_DEPRECATED = 'deprecated' # Block States BLOCK_DISABLED = 0 diff --git a/grc/core/Element.py b/grc/core/Element.py index b96edb0a72..67c36e12b4 100644 --- a/grc/core/Element.py +++ b/grc/core/Element.py @@ -100,6 +100,7 @@ class Element(object): is_flow_graph = False is_block = False + is_dummy_block = False is_connection = False @@ -107,3 +108,7 @@ class Element(object): is_port = False is_param = False + + is_variable = False + + is_import = False diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py index 2f33baf8d0..949eecaa71 100644 --- a/grc/core/FlowGraph.py +++ b/grc/core/FlowGraph.py @@ -18,7 +18,7 @@ import imp import time from itertools import ifilter, chain -from operator import methodcaller +from operator import methodcaller, attrgetter import re @@ -27,7 +27,6 @@ from .Constants import FLOW_GRAPH_FILE_FORMAT_VERSION from .Element import Element from .utils import odict, expr_utils -_variable_matcher = re.compile('^(variable\w*)$') _parameter_matcher = re.compile('^(parameter)$') _monitors_searcher = re.compile('(ctrlport_monitor)') _bussink_searcher = re.compile('^(bus_sink)$') @@ -72,32 +71,31 @@ class FlowGraph(Element): ############################################## def get_imports(self): """ - Get a set of all import statments in this flow graph namespace. + Get a set of all import statements in this flow graph namespace. Returns: a set of import statements """ - imports = sum([block.get_imports() for block in self.get_enabled_blocks()], []) - imports = sorted(set(imports)) - return imports + imports = sum([block.get_imports() for block in self.iter_enabled_blocks()], []) + return sorted(set(imports)) def get_variables(self): """ Get a list of all variables in this flow graph namespace. - Exclude paramterized variables. + Exclude parameterized variables. Returns: a sorted list of variable blocks in order of dependency (indep -> dep) """ - variables = filter(lambda b: _variable_matcher.match(b.get_key()), self.iter_enabled_blocks()) + variables = 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): """ - Get a list of all paramterized variables in this flow graph namespace. + Get a list of all parameterized variables in this flow graph namespace. Returns: - a list of paramterized variables + a list of parameterized variables """ parameters = filter(lambda b: _parameter_matcher.match(b.get_key()), self.iter_enabled_blocks()) return parameters diff --git a/grc/core/Messages.py b/grc/core/Messages.py index da50487e5b..8daa12c33f 100644 --- a/grc/core/Messages.py +++ b/grc/core/Messages.py @@ -66,10 +66,6 @@ def send_init(platform): ) -def send_page_switch(file_path): - send('\nShowing: "%s"\n' % file_path) - - def send_xml_errors_if_any(xml_failures): if xml_failures: send('\nXML parser: Found {0} erroneous XML file{1} while loading the ' diff --git a/grc/core/generator/Generator.py b/grc/core/generator/Generator.py index 641bdea6e6..fb7a3afb99 100644 --- a/grc/core/generator/Generator.py +++ b/grc/core/generator/Generator.py @@ -145,6 +145,10 @@ class TopBlockGenerator(object): filter(lambda b: b.get_enabled() and not b.get_bypassed(), fg.blocks), lambda b: b.get_id(), _get_block_sort_text ) + deprecated_block_keys = set(block.get_name() for block in blocks_all if block.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) @@ -210,17 +214,23 @@ class TopBlockGenerator(object): connection_templates = fg.get_parent().connection_templates msgs = filter(lambda c: c.is_msg(), fg.get_enabled_connections()) + # List of variable names var_ids = [var.get_id() for var in parameters + variables] replace_dict = dict((var_id, 'self.' + var_id) for var_id in var_ids) callbacks_all = [] for block in blocks_all: callbacks_all.extend(expr_utils.expr_replace(cb, replace_dict) for cb in block.get_callbacks()) + # Map var id to callbacks + def uses_var_id(): + used = expr_utils.get_variable_dependencies(callback, [var_id]) + return used and 'self.' + var_id in callback # callback might contain var_id itself + callbacks = {} for var_id in var_ids: - callbacks[var_id] = [callback for callback in callbacks_all - if expr_utils.get_variable_dependencies(callback, ['self.' + var_id])] + callbacks[var_id] = [callback for callback in callbacks_all if uses_var_id()] + # Load the namespace namespace = { 'title': title, diff --git a/grc/core/utils/expr_utils.py b/grc/core/utils/expr_utils.py index c5069583e0..2059ceff9f 100644 --- a/grc/core/utils/expr_utils.py +++ b/grc/core/utils/expr_utils.py @@ -18,7 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ import string -VAR_CHARS = string.letters + string.digits + '_.' +VAR_CHARS = string.letters + string.digits + '_' class graph(object): @@ -56,7 +56,7 @@ class graph(object): return self._graph[node_key] -def expr_split(expr): +def expr_split(expr, var_chars=VAR_CHARS): """ Split up an expression by non alphanumeric characters, including underscore. Leave strings in-tact. @@ -72,7 +72,7 @@ def expr_split(expr): tok = '' quote = '' for char in expr: - if quote or char in VAR_CHARS: + if quote or char in var_chars: if char == quote: quote = '' tok += char @@ -99,7 +99,7 @@ def expr_replace(expr, replace_dict): Returns: a new expression with the prepend """ - expr_splits = expr_split(expr) + expr_splits = expr_split(expr, var_chars=VAR_CHARS + '.') for i, es in enumerate(expr_splits): if es in replace_dict.keys(): expr_splits[i] = replace_dict[es] @@ -189,8 +189,3 @@ 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] - - -if __name__ == '__main__': - for i in sort_variables({'x': '1', 'y': 'x+1', 'a': 'x+y', 'b': 'y+1', 'c': 'a+b+x+y'}): - print i diff --git a/grc/gui/ActionHandler.py b/grc/gui/ActionHandler.py index 077786d4b4..2b39079f76 100644 --- a/grc/gui/ActionHandler.py +++ b/grc/gui/ActionHandler.py @@ -25,7 +25,7 @@ import subprocess from . import Dialogs, Preferences, Actions, Executor, Constants from .FileDialogs import (OpenFlowGraphFileDialog, SaveFlowGraphFileDialog, - SaveReportsFileDialog, SaveScreenShotDialog, + SaveConsoleFileDialog, SaveScreenShotDialog, OpenQSSFileDialog) from .MainWindow import MainWindow from .ParserErrorsDialog import ParserErrorsDialog @@ -61,13 +61,13 @@ class ActionHandler: 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_flow_graph = self.main_window.get_flow_graph self.get_focus_flag = self.main_window.get_focus_flag #setup the messages - Messages.register_messenger(self.main_window.add_report_line) + Messages.register_messenger(self.main_window.add_console_line) Messages.send_init(platform) #initialize self.init_file_paths = file_paths + self.init = False Actions.APPLICATION_INITIALIZE() def _handle_key_press(self, widget, event): @@ -104,6 +104,14 @@ 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 + + def flow_graph_update(fg=flow_graph): + main.vars.update_gui() + fg.update() + ################################################## # Initialize/Quit ################################################## @@ -112,12 +120,13 @@ class ActionHandler: self.init_file_paths = 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: self.main_window.new_page(file_path) #load pages from file paths + if file_path: main.new_page(file_path) #load pages from file paths if Preferences.file_open() in self.init_file_paths: - self.main_window.new_page(Preferences.file_open(), show=True) - if not self.get_page(): self.main_window.new_page() #ensure that at least a blank page exists + main.new_page(Preferences.file_open(), show=True) + if not self.get_page(): + main.new_page() # ensure that at least a blank page exists - self.main_window.btwin.search_entry.hide() + main.btwin.search_entry.hide() # Disable all actions, then re-enable a few for action in Actions.get_all_actions(): @@ -128,14 +137,17 @@ class ActionHandler: 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.TOGGLE_CONSOLE_WINDOW, Actions.TOGGLE_HIDE_DISABLED_BLOCKS, Actions.TOOLS_RUN_FDESIGN, Actions.TOGGLE_SCROLL_LOCK, - Actions.CLEAR_REPORTS, Actions.SAVE_REPORTS, + Actions.CLEAR_CONSOLE, Actions.SAVE_CONSOLE, 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.FLOW_GRAPH_OPEN_QSS_THEME, + Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR, + Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR, + Actions.TOGGLE_HIDE_VARIABLES, Actions.SELECT_ALL, ): action.set_sensitive(True) @@ -144,9 +156,9 @@ class ActionHandler: if ParseXML.xml_failures: Messages.send_xml_errors_if_any(ParseXML.xml_failures) Actions.XML_PARSER_ERRORS_DISPLAY.set_sensitive(True) - + self.init = True elif action == Actions.APPLICATION_QUIT: - if self.main_window.close_pages(): + if main.close_pages(): gtk.main_quit() exit(0) ################################################## @@ -155,27 +167,27 @@ class ActionHandler: elif action == Actions.ELEMENT_SELECT: pass #do nothing, update routines below elif action == Actions.NOTHING_SELECT: - self.get_flow_graph().unselect() + flow_graph.unselect() elif action == Actions.SELECT_ALL: - self.get_flow_graph().select_all() + flow_graph.select_all() ################################################## # Enable/Disable ################################################## elif action == Actions.BLOCK_ENABLE: - if self.get_flow_graph().enable_selected(True): - self.get_flow_graph().update() - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.get_page().set_saved(False) + if flow_graph.enable_selected(True): + flow_graph_update() + page.get_state_cache().save_new_state(flow_graph.export_data()) + page.set_saved(False) elif action == Actions.BLOCK_DISABLE: - if self.get_flow_graph().enable_selected(False): - self.get_flow_graph().update() - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.get_page().set_saved(False) + if flow_graph.enable_selected(False): + flow_graph_update() + page.get_state_cache().save_new_state(flow_graph.export_data()) + page.set_saved(False) elif action == Actions.BLOCK_BYPASS: - if self.get_flow_graph().bypass_selected(): - self.get_flow_graph().update() - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.get_page().set_saved(False) + if flow_graph.bypass_selected(): + flow_graph_update() + page.get_state_cache().save_new_state(flow_graph.export_data()) + page.set_saved(False) ################################################## # Cut/Copy/Paste ################################################## @@ -183,20 +195,20 @@ class ActionHandler: Actions.BLOCK_COPY() Actions.ELEMENT_DELETE() elif action == Actions.BLOCK_COPY: - self.clipboard = self.get_flow_graph().copy_to_clipboard() + self.clipboard = flow_graph.copy_to_clipboard() elif action == Actions.BLOCK_PASTE: if self.clipboard: - self.get_flow_graph().paste_from_clipboard(self.clipboard) - self.get_flow_graph().update() - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.get_page().set_saved(False) + 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) ################################################## # Create heir block ################################################## elif action == Actions.BLOCK_CREATE_HIER: # keeping track of coordinates for pasting later - coords = self.get_flow_graph().get_selected_blocks()[0].get_coordinate() + coords = flow_graph.get_selected_blocks()[0].get_coordinate() x,y = coords x_min = x y_min = y @@ -205,15 +217,15 @@ class ActionHandler: params = []; # Save the state of the leaf blocks - for block in self.get_flow_graph().get_selected_blocks(): + for block in flow_graph.get_selected_blocks(): # Check for string variables within the blocks for param in block.get_params(): - for variable in self.get_flow_graph().get_variables(): + 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.get_id(): params.append(param.get_value()) - for flow_param in self.get_flow_graph().get_parameters(): + 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.get_id(): params.append(param.get_value()) @@ -233,39 +245,39 @@ class ActionHandler: sink_id = connection.get_sink().get_parent().get_id() # If connected block is not in the list of selected blocks create a pad for it - if self.get_flow_graph().get_block(source_id) not in self.get_flow_graph().get_selected_blocks(): + 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'}) - if self.get_flow_graph().get_block(sink_id) not in self.get_flow_graph().get_selected_blocks(): + 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'}) # Copy the selected blocks and paste them into a new page # then move the flowgraph to a reasonable position Actions.BLOCK_COPY() - self.main_window.new_page() + main.new_page() Actions.BLOCK_PASTE() coords = (x_min,y_min) - self.get_flow_graph().move_selected(coords) + flow_graph.move_selected(coords) # Set flow graph to heir block type - top_block = self.get_flow_graph().get_block("top_block") + top_block = flow_graph.get_block("top_block") top_block.get_param('generate_options').set_value('hb') # this needs to be a unique name top_block.get_param('id').set_value('new_heir') # Remove the default samp_rate variable block that is created - remove_me = self.get_flow_graph().get_block("samp_rate") - self.get_flow_graph().remove_element(remove_me) + 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 = self.get_flow_graph().add_new_block('parameter',(x_pos,10)) - param_block = self.get_flow_graph().get_block(param_id) + param_id = flow_graph.add_new_block('parameter',(x_pos,10)) + param_block = flow_graph.get_block(param_id) param_block.get_param('id').set_value(param) x_pos = x_pos + 100 @@ -274,13 +286,13 @@ class ActionHandler: if pad['direction'] == 'sink': # Add new PAD_SINK block to the canvas - pad_id = self.get_flow_graph().add_new_block('pad_sink', pad['coord']) + pad_id = flow_graph.add_new_block('pad_sink', pad['coord']) # setup the references to the sink and source - pad_block = self.get_flow_graph().get_block(pad_id) + pad_block = flow_graph.get_block(pad_id) pad_sink = pad_block.get_sinks()[0] - source_block = self.get_flow_graph().get_block(pad['block_id']) + source_block = flow_graph.get_block(pad['block_id']) source = source_block.get_source(pad['key']) # Ensure the port types match @@ -292,16 +304,16 @@ class ActionHandler: pad_block.type_controller_modify(1) # Connect the pad to the proper sinks - new_connection = self.get_flow_graph().connect(source,pad_sink) + new_connection = flow_graph.connect(source,pad_sink) elif pad['direction'] == 'source': - pad_id = self.get_flow_graph().add_new_block('pad_source', pad['coord']) + pad_id = flow_graph.add_new_block('pad_source', pad['coord']) # setup the references to the sink and source - pad_block = self.get_flow_graph().get_block(pad_id) + pad_block = flow_graph.get_block(pad_id) pad_source = pad_block.get_sources()[0] - sink_block = self.get_flow_graph().get_block(pad['block_id']) + sink_block = flow_graph.get_block(pad['block_id']) sink = sink_block.get_sink(pad['key']) # Ensure the port types match @@ -312,140 +324,172 @@ class ActionHandler: pad_block.type_controller_modify(1) # Connect the pad to the proper sinks - new_connection = self.get_flow_graph().connect(pad_source,sink) + new_connection = flow_graph.connect(pad_source,sink) # update the new heir block flow graph - self.get_flow_graph().update() + flow_graph_update() ################################################## # Move/Rotate/Delete/Create ################################################## elif action == Actions.BLOCK_MOVE: - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.get_page().set_saved(False) + page.get_state_cache().save_new_state(flow_graph.export_data()) + page.set_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) elif action == Actions.BLOCK_ROTATE_CCW: - if self.get_flow_graph().rotate_selected(90): - self.get_flow_graph().update() - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.get_page().set_saved(False) + if flow_graph.rotate_selected(90): + flow_graph_update() + page.get_state_cache().save_new_state(flow_graph.export_data()) + page.set_saved(False) elif action == Actions.BLOCK_ROTATE_CW: - if self.get_flow_graph().rotate_selected(-90): - self.get_flow_graph().update() - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.get_page().set_saved(False) + if flow_graph.rotate_selected(-90): + flow_graph_update() + page.get_state_cache().save_new_state(flow_graph.export_data()) + page.set_saved(False) elif action == Actions.ELEMENT_DELETE: - if self.get_flow_graph().remove_selected(): - self.get_flow_graph().update() - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + if flow_graph.remove_selected(): + flow_graph_update() + page.get_state_cache().save_new_state(flow_graph.export_data()) Actions.NOTHING_SELECT() - self.get_page().set_saved(False) + page.set_saved(False) elif action == Actions.ELEMENT_CREATE: - self.get_flow_graph().update() - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) + flow_graph_update() + page.get_state_cache().save_new_state(flow_graph.export_data()) Actions.NOTHING_SELECT() - self.get_page().set_saved(False) + page.set_saved(False) elif action == Actions.BLOCK_INC_TYPE: - if self.get_flow_graph().type_controller_modify_selected(1): - self.get_flow_graph().update() - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.get_page().set_saved(False) + 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) elif action == Actions.BLOCK_DEC_TYPE: - if self.get_flow_graph().type_controller_modify_selected(-1): - self.get_flow_graph().update() - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.get_page().set_saved(False) + 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) elif action == Actions.PORT_CONTROLLER_INC: - if self.get_flow_graph().port_controller_modify_selected(1): - self.get_flow_graph().update() - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.get_page().set_saved(False) + 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) elif action == Actions.PORT_CONTROLLER_DEC: - if self.get_flow_graph().port_controller_modify_selected(-1): - self.get_flow_graph().update() - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.get_page().set_saved(False) + 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) ################################################## # Window stuff ################################################## elif action == Actions.ABOUT_WINDOW_DISPLAY: - platform = self.get_flow_graph().get_parent() + platform = flow_graph.get_parent() Dialogs.AboutDialog(platform.config) elif action == Actions.HELP_WINDOW_DISPLAY: Dialogs.HelpDialog() elif action == Actions.TYPES_WINDOW_DISPLAY: - Dialogs.TypesDialog(self.get_flow_graph().get_parent()) + Dialogs.TypesDialog(flow_graph.get_parent()) elif action == Actions.ERRORS_WINDOW_DISPLAY: - Dialogs.ErrorsDialog(self.get_flow_graph()) - elif action == Actions.TOGGLE_REPORTS_WINDOW: - if action.get_active(): - self.main_window.reports_scrolled_window.show() - else: - self.main_window.reports_scrolled_window.hide() + Dialogs.ErrorsDialog(flow_graph) + elif action == Actions.TOGGLE_CONSOLE_WINDOW: + main.update_panel_visibility(main.CONSOLE, action.get_active()) action.save_to_preferences() elif action == Actions.TOGGLE_BLOCKS_WINDOW: - if action.get_active(): - self.main_window.btwin.show() - else: - self.main_window.btwin.hide() + main.update_panel_visibility(main.BLOCKS, action.get_active()) action.save_to_preferences() elif action == Actions.TOGGLE_SCROLL_LOCK: active = action.get_active() - self.main_window.text_display.scroll_lock = active + main.text_display.scroll_lock = active if active: - self.main_window.text_display.scroll_to_end() + main.text_display.scroll_to_end() action.save_to_preferences() - elif action == Actions.CLEAR_REPORTS: - self.main_window.text_display.clear() - elif action == Actions.SAVE_REPORTS: - file_path = SaveReportsFileDialog(self.get_page().get_file_path()).run() + elif action == Actions.CLEAR_CONSOLE: + main.text_display.clear() + elif action == Actions.SAVE_CONSOLE: + file_path = SaveConsoleFileDialog(page.get_file_path()).run() if file_path is not None: - self.main_window.text_display.save(file_path) + main.text_display.save(file_path) elif action == Actions.TOGGLE_HIDE_DISABLED_BLOCKS: Actions.NOTHING_SELECT() elif action == Actions.TOGGLE_AUTO_HIDE_PORT_LABELS: action.save_to_preferences() - for page in self.main_window.get_pages(): + for page in main.get_pages(): page.get_flow_graph().create_shapes() - elif action == Actions.TOGGLE_SNAP_TO_GRID: + elif action in (Actions.TOGGLE_SNAP_TO_GRID, + Actions.TOGGLE_SHOW_BLOCK_COMMENTS, + Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB): action.save_to_preferences() - elif action == Actions.TOGGLE_SHOW_BLOCK_COMMENTS: + elif action == Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY: action.save_to_preferences() - elif action == Actions.TOGGLE_SHOW_CODE_PREVIEW_TAB: + for page in main.get_pages(): + flow_graph_update(page.get_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() + Actions.NOTHING_SELECT() action.save_to_preferences() - elif action == Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY: + elif action == Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR: + # See if the variables are hidden + if Actions.TOGGLE_HIDE_VARIABLES.get_active(): + # Force this to be shown + main.update_panel_visibility(main.VARIABLES, True) + action.set_active(True) + action.set_sensitive(False) + else: + if action.get_sensitive(): + main.update_panel_visibility(main.VARIABLES, action.get_active()) + else: # This is occurring after variables are un-hidden + # Leave it enabled + action.set_sensitive(True) + action.set_active(True) + #Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR.set_sensitive(action.get_active()) action.save_to_preferences() - for page in self.main_window.get_pages(): - page.get_flow_graph().update() + 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.run() + md.destroy() + action.save_to_preferences() ################################################## # Param Modifications ################################################## elif action == Actions.BLOCK_PARAM_MODIFY: - selected_block = self.get_flow_graph().get_selected_block() + if action.args: + selected_block = action.args[0] + 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 response = self.dialog.run() if response in (gtk.RESPONSE_APPLY, gtk.RESPONSE_ACCEPT): - self.get_flow_graph().update() - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.get_page().set_saved(False) + flow_graph_update() + page.get_state_cache().save_new_state(flow_graph.export_data()) + page.set_saved(False) else: # restore the current state - n = self.get_page().get_state_cache().get_current_state() - self.get_flow_graph().import_data(n) - self.get_flow_graph().update() + n = page.get_state_cache().get_current_state() + flow_graph.import_data(n) + flow_graph_update() if response == gtk.RESPONSE_APPLY: # null action, that updates the main window Actions.ELEMENT_SELECT() self.dialog.destroy() self.dialog = None elif action == Actions.EXTERNAL_UPDATE: - self.get_page().get_state_cache().save_new_state(self.get_flow_graph().export_data()) - self.get_flow_graph().update() + page.get_state_cache().save_new_state(flow_graph.export_data()) + flow_graph_update() if self.dialog is not None: self.dialog.update_gui(force=True) - self.get_page().set_saved(False) + page.set_saved(False) + elif action == Actions.VARIABLE_EDITOR_UPDATE: + page.get_state_cache().save_new_state(flow_graph.export_data()) + flow_graph_update() + page.set_saved(False) ################################################## # View Parser Errors ################################################## @@ -455,35 +499,36 @@ class ActionHandler: # Undo/Redo ################################################## elif action == Actions.FLOW_GRAPH_UNDO: - n = self.get_page().get_state_cache().get_prev_state() + n = page.get_state_cache().get_prev_state() if n: - self.get_flow_graph().unselect() - self.get_flow_graph().import_data(n) - self.get_flow_graph().update() - self.get_page().set_saved(False) + flow_graph.unselect() + flow_graph.import_data(n) + flow_graph_update() + page.set_saved(False) elif action == Actions.FLOW_GRAPH_REDO: - n = self.get_page().get_state_cache().get_next_state() + n = page.get_state_cache().get_next_state() if n: - self.get_flow_graph().unselect() - self.get_flow_graph().import_data(n) - self.get_flow_graph().update() - self.get_page().set_saved(False) + flow_graph.unselect() + flow_graph.import_data(n) + flow_graph_update() + page.set_saved(False) ################################################## # New/Open/Save/Close ################################################## elif action == Actions.FLOW_GRAPH_NEW: - self.main_window.new_page() + main.new_page() if args: - self.get_flow_graph()._options_block.get_param('generate_options').set_value(args[0]) - self.get_flow_graph().update() + 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(self.get_page().get_file_path()).run() + 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 for i,file_path in enumerate(file_paths): - self.main_window.new_page(file_path, show=(i==0)) + main.new_page(file_path, show=(i==0)) Preferences.add_recent_file(file_path) - self.main_window.tool_bar.refresh_submenus() - self.main_window.menu_bar.refresh_submenus() + 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 + @@ -496,97 +541,103 @@ class ActionHandler: except Exception as e: Messages.send("Failed to save QSS preference: " + str(e)) elif action == Actions.FLOW_GRAPH_CLOSE: - self.main_window.close_page() + main.close_page() elif action == Actions.FLOW_GRAPH_SAVE: #read-only or undefined file path, do save-as - if self.get_page().get_read_only() or not self.get_page().get_file_path(): + if page.get_read_only() or not page.get_file_path(): Actions.FLOW_GRAPH_SAVE_AS() #otherwise try to save else: try: - ParseXML.to_file(self.get_flow_graph().export_data(), self.get_page().get_file_path()) - self.get_flow_graph().grc_file_path = self.get_page().get_file_path() - self.get_page().set_saved(True) + ParseXML.to_file(flow_graph.export_data(), page.get_file_path()) + flow_graph.grc_file_path = page.get_file_path() + page.set_saved(True) except IOError: - Messages.send_fail_save(self.get_page().get_file_path()) - self.get_page().set_saved(False) + Messages.send_fail_save(page.get_file_path()) + page.set_saved(False) elif action == Actions.FLOW_GRAPH_SAVE_AS: - file_path = SaveFlowGraphFileDialog(self.get_page().get_file_path()).run() + file_path = SaveFlowGraphFileDialog(page.get_file_path()).run() if file_path is not None: - self.get_page().set_file_path(file_path) + page.set_file_path(file_path) Actions.FLOW_GRAPH_SAVE() Preferences.add_recent_file(file_path) - self.main_window.tool_bar.refresh_submenus() - self.main_window.menu_bar.refresh_submenus() + main.tool_bar.refresh_submenus() + main.menu_bar.refresh_submenus() elif action == Actions.FLOW_GRAPH_SCREEN_CAPTURE: - file_path, background_transparent = SaveScreenShotDialog(self.get_page().get_file_path()).run() + file_path, background_transparent = SaveScreenShotDialog(page.get_file_path()).run() if file_path is not None: - pixbuf = self.get_flow_graph().get_drawing_area().get_screenshot(background_transparent) + pixbuf = flow_graph.get_drawing_area().get_screenshot(background_transparent) pixbuf.save(file_path, Constants.IMAGE_FILE_EXTENSION[1:]) ################################################## # Gen/Exec/Stop ################################################## elif action == Actions.FLOW_GRAPH_GEN: - if not self.get_page().get_proc(): - if not self.get_page().get_saved() or not self.get_page().get_file_path(): + 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 self.get_page().get_saved() and self.get_page().get_file_path(): - generator = self.get_page().get_generator() + if page.get_saved() and page.get_file_path(): + generator = page.get_generator() try: Messages.send_start_gen(generator.get_file_path()) generator.write() - except Exception,e: Messages.send_fail_gen(e) - else: self.generator = None + except Exception as e: + Messages.send_fail_gen(e) + else: + self.generator = None elif action == Actions.FLOW_GRAPH_EXEC: - if not self.get_page().get_proc(): + if not page.get_proc(): 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 self.get_page().get_saved() and self.get_page().get_file_path(): - Executor.ExecFlowGraphThread(self) + if page.get_saved() and page.get_file_path(): + Executor.ExecFlowGraphThread( + flow_graph_page=page, + xterm_executable=xterm, + callback=self.update_exec_stop + ) elif action == Actions.FLOW_GRAPH_KILL: - if self.get_page().get_proc(): + if page.get_proc(): try: - self.get_page().get_proc().kill() + page.get_proc().kill() except: - print "could not kill process: %d" % self.get_page().get_proc().pid + print "could not kill process: %d" % page.get_proc().pid elif action == Actions.PAGE_CHANGE: # pass and run the global actions pass elif action == Actions.RELOAD_BLOCKS: self.platform.load_blocks() - self.main_window.btwin.clear() - self.platform.load_block_tree(self.main_window.btwin) + main.btwin.clear() + self.platform.load_block_tree(main.btwin) Actions.XML_PARSER_ERRORS_DISPLAY.set_sensitive(bool( ParseXML.xml_failures)) Messages.send_xml_errors_if_any(ParseXML.xml_failures) # Force a redraw of the graph, by getting the current state and re-importing it - self.main_window.update_pages() + main.update_pages() elif action == Actions.FIND_BLOCKS: - self.main_window.btwin.show() - self.main_window.btwin.search_entry.show() - self.main_window.btwin.search_entry.grab_focus() + 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 self.get_flow_graph().get_selected_blocks(): + for b in flow_graph.get_selected_blocks(): if b._grc_source: - self.main_window.new_page(b._grc_source, show=True) + main.new_page(b._grc_source, show=True) elif action == Actions.BUSSIFY_SOURCES: n = {'name':'bus', 'type':'bus'} - for b in self.get_flow_graph().get_selected_blocks(): + for b in flow_graph.get_selected_blocks(): b.bussify(n, 'source') - self.get_flow_graph()._old_selected_port = None - self.get_flow_graph()._new_selected_port = None + flow_graph._old_selected_port = None + flow_graph._new_selected_port = None Actions.ELEMENT_CREATE() elif action == Actions.BUSSIFY_SINKS: n = {'name':'bus', 'type':'bus'} - for b in self.get_flow_graph().get_selected_blocks(): + for b in flow_graph.get_selected_blocks(): b.bussify(n, 'sink') - self.get_flow_graph()._old_selected_port = None - self.get_flow_graph()._new_selected_port = None + flow_graph._old_selected_port = None + flow_graph._new_selected_port = None Actions.ELEMENT_CREATE() elif action == Actions.TOOLS_RUN_FDESIGN: @@ -598,15 +649,22 @@ class ActionHandler: ################################################## # Global Actions for all States ################################################## - selected_block = self.get_flow_graph().get_selected_block() - selected_blocks = self.get_flow_graph().get_selected_blocks() + page = main.get_page() # page and flowgraph might have changed + flow_graph = page.get_flow_graph() if page else None + + selected_blocks = flow_graph.get_selected_blocks() + selected_block = selected_blocks[0] if selected_blocks else None #update general buttons - Actions.ERRORS_WINDOW_DISPLAY.set_sensitive(not self.get_flow_graph().is_valid()) - Actions.ELEMENT_DELETE.set_sensitive(bool(self.get_flow_graph().get_selected_elements())) + Actions.ERRORS_WINDOW_DISPLAY.set_sensitive(not flow_graph.is_valid()) + Actions.ELEMENT_DELETE.set_sensitive(bool(flow_graph.get_selected_elements())) Actions.BLOCK_PARAM_MODIFY.set_sensitive(bool(selected_block)) Actions.BLOCK_ROTATE_CCW.set_sensitive(bool(selected_blocks)) Actions.BLOCK_ROTATE_CW.set_sensitive(bool(selected_blocks)) + #update alignment options + for act in Actions.BLOCK_ALIGNMENTS: + if act: + act.set_sensitive(len(selected_blocks) > 1) #update cut/copy/paste Actions.BLOCK_CUT.set_sensitive(bool(selected_blocks)) Actions.BLOCK_COPY.set_sensitive(bool(selected_blocks)) @@ -631,17 +689,17 @@ class ActionHandler: #set the exec and stop buttons self.update_exec_stop() #saved status - Actions.FLOW_GRAPH_SAVE.set_sensitive(not self.get_page().get_saved()) - self.main_window.update() + Actions.FLOW_GRAPH_SAVE.set_sensitive(not page.get_saved()) + main.update() try: #set the size of the flow graph area (if changed) - new_size = (self.get_flow_graph().get_option('window_size') or + new_size = (flow_graph.get_option('window_size') or self.platform.config.default_canvas_size) - if self.get_flow_graph().get_size() != tuple(new_size): - self.get_flow_graph().set_size(*new_size) + if flow_graph.get_size() != tuple(new_size): + flow_graph.set_size(*new_size) except: pass #draw the flow graph - self.get_flow_graph().update_selected() - self.get_flow_graph().queue_draw() + flow_graph.update_selected() + flow_graph.queue_draw() return True #action was handled def update_exec_stop(self): @@ -649,7 +707,7 @@ class ActionHandler: Update the exec and stop buttons. Lock and unlock the mutex for race conditions with exec flow graph threads. """ - sensitive = self.get_flow_graph().is_valid() and not self.get_page().get_proc() + sensitive = self.main_window.get_flow_graph().is_valid() and not self.get_page().get_proc() 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) diff --git a/grc/gui/Actions.py b/grc/gui/Actions.py index 354e536a82..9b2af36b93 100644 --- a/grc/gui/Actions.py +++ b/grc/gui/Actions.py @@ -92,6 +92,7 @@ class _ActionBase(object): self.set_accel_group(get_accel_group()) self.set_accel_path(accel_path) gtk.accel_map_add_entry(accel_path, keyval, mod_mask) + self.args = None def __str__(self): """ @@ -105,10 +106,11 @@ class _ActionBase(object): def __repr__(self): return str(self) - def __call__(self): + def __call__(self, *args): """ Emit the activate signal when called with (). """ + self.args = args self.emit('activate') @@ -171,6 +173,7 @@ class ToggleAction(gtk.ToggleAction, _ActionBase): ######################################################################## PAGE_CHANGE = Action() EXTERNAL_UPDATE = Action() +VARIABLE_EDITOR_UPDATE = Action() FLOW_GRAPH_NEW = Action( label='_New', tooltip='Create a new flow graph', @@ -253,6 +256,45 @@ BLOCK_ROTATE_CW = Action( stock_id=gtk.STOCK_GO_FORWARD, keypresses=(gtk.keysyms.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), +) +BLOCK_VALIGN_MIDDLE = Action( + label='Vertical Align Middle', + tooltip='Align centers of selected blocks vertically', + keypresses=(gtk.keysyms.m, gtk.gdk.SHIFT_MASK), +) +BLOCK_VALIGN_BOTTOM = Action( + label='Vertical Align Bottom', + tooltip='Align bottoms of selected blocks', + keypresses=(gtk.keysyms.b, gtk.gdk.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), +) +BLOCK_HALIGN_CENTER = Action( + label='Horizontal Align Center', + tooltip='Align centers of selected blocks horizontally', + keypresses=(gtk.keysyms.c, gtk.gdk.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), +) +BLOCK_ALIGNMENTS = [ + BLOCK_VALIGN_TOP, + BLOCK_VALIGN_MIDDLE, + BLOCK_VALIGN_BOTTOM, + None, + BLOCK_HALIGN_LEFT, + BLOCK_HALIGN_CENTER, + BLOCK_HALIGN_RIGHT, +] BLOCK_PARAM_MODIFY = Action( label='_Properties', tooltip='Modify params for the selected block', @@ -288,6 +330,26 @@ TOGGLE_HIDE_DISABLED_BLOCKS = ToggleAction( stock_id=gtk.STOCK_MISSING_IMAGE, keypresses=(gtk.keysyms.d, gtk.gdk.CONTROL_MASK), ) +TOGGLE_HIDE_VARIABLES = ToggleAction( + label='Hide Variables', + tooltip='Hide all variable blocks', + preference_name='hide_variables', + default=False, +) +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, + default=True, + keypresses=(gtk.keysyms.e, gtk.gdk.CONTROL_MASK), + preference_name='variable_editor_visable', +) +TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR = ToggleAction( + label='Move the Variable Editor to the Sidebar', + tooltip='Move the variable editor to the sidebar', + default=False, + preference_name='variable_editor_sidebar', +) TOGGLE_AUTO_HIDE_PORT_LABELS = ToggleAction( label='Auto-Hide _Port Labels', tooltip='Automatically hide port labels', @@ -340,11 +402,11 @@ ERRORS_WINDOW_DISPLAY = Action( tooltip='View flow graph errors', stock_id=gtk.STOCK_DIALOG_ERROR, ) -TOGGLE_REPORTS_WINDOW = ToggleAction( - label='Show _Reports Panel', - tooltip='Toggle visibility of the Report widget', +TOGGLE_CONSOLE_WINDOW = ToggleAction( + label='Show _Console Panel', + tooltip='Toggle visibility of the console', keypresses=(gtk.keysyms.r, gtk.gdk.CONTROL_MASK), - preference_name='reports_window_visible' + preference_name='console_window_visible' ) TOGGLE_BLOCKS_WINDOW = ToggleAction( label='Show _Block Tree Panel', @@ -353,8 +415,8 @@ TOGGLE_BLOCKS_WINDOW = ToggleAction( preference_name='blocks_window_visible' ) TOGGLE_SCROLL_LOCK = ToggleAction( - label='Reports Scroll _Lock', - tooltip='Toggle scroll lock for the report window', + label='Console Scroll _Lock', + tooltip='Toggle scroll lock for the console window', preference_name='scroll_lock' ) ABOUT_WINDOW_DISPLAY = Action( @@ -421,14 +483,14 @@ FIND_BLOCKS = Action( keypresses=(gtk.keysyms.f, gtk.gdk.CONTROL_MASK, gtk.keysyms.slash, NO_MODS_MASK), ) -CLEAR_REPORTS = Action( - label='_Clear Reports', - tooltip='Clear Reports', +CLEAR_CONSOLE = Action( + label='_Clear Console', + tooltip='Clear Console', stock_id=gtk.STOCK_CLEAR, ) -SAVE_REPORTS = Action( - label='_Save Reports', - tooltip='Save Reports', +SAVE_CONSOLE = Action( + label='_Save Console', + tooltip='Save Console', stock_id=gtk.STOCK_SAVE, ) OPEN_HIER = Action( diff --git a/grc/gui/Bars.py b/grc/gui/Bars.py index cb101111cd..a4819b973c 100644 --- a/grc/gui/Bars.py +++ b/grc/gui/Bars.py @@ -31,6 +31,7 @@ TOOLBAR_LIST = ( Actions.FLOW_GRAPH_SAVE, Actions.FLOW_GRAPH_CLOSE, None, + Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR, Actions.FLOW_GRAPH_SCREEN_CAPTURE, None, Actions.BLOCK_CUT, @@ -86,6 +87,7 @@ MENU_BAR_LIST = ( None, Actions.BLOCK_ROTATE_CCW, Actions.BLOCK_ROTATE_CW, + (gtk.Action('Align', '_Align', None, None), Actions.BLOCK_ALIGNMENTS), None, Actions.BLOCK_ENABLE, Actions.BLOCK_DISABLE, @@ -96,10 +98,14 @@ MENU_BAR_LIST = ( (gtk.Action('View', '_View', None, None), [ Actions.TOGGLE_BLOCKS_WINDOW, None, - Actions.TOGGLE_REPORTS_WINDOW, + Actions.TOGGLE_CONSOLE_WINDOW, Actions.TOGGLE_SCROLL_LOCK, - Actions.SAVE_REPORTS, - Actions.CLEAR_REPORTS, + Actions.SAVE_CONSOLE, + Actions.CLEAR_CONSOLE, + None, + Actions.TOGGLE_HIDE_VARIABLES, + Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR, + Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR_SIDEBAR, None, Actions.TOGGLE_HIDE_DISABLED_BLOCKS, Actions.TOGGLE_AUTO_HIDE_PORT_LABELS, diff --git a/grc/gui/Block.py b/grc/gui/Block.py index 815982c318..55c8805fae 100644 --- a/grc/gui/Block.py +++ b/grc/gui/Block.py @@ -22,13 +22,8 @@ pygtk.require('2.0') import gtk import pango -from . import Actions, Colors, Utils +from . import Actions, Colors, Utils, Constants -from .Constants import ( - BLOCK_LABEL_PADDING, PORT_SPACING, PORT_SEPARATION, LABEL_SEPARATION, - PORT_BORDER_SEPARATION, POSSIBLE_ROTATIONS, BLOCK_FONT, PARAM_FONT, - BORDER_PROXIMITY_SENSITIVITY -) from . Element import Element from ..core.Param import num_to_str from ..core.utils import odict @@ -98,18 +93,19 @@ 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 - BORDER_PROXIMITY_SENSITIVITY: - x = fgW - BORDER_PROXIMITY_SENSITIVITY + elif x >= fgW - proximity: + x = fgW - proximity if y <= 0: y = 0 - elif y >= fgH - BORDER_PROXIMITY_SENSITIVITY: - y = fgH - BORDER_PROXIMITY_SENSITIVITY + elif y >= fgH - proximity: + y = fgH - proximity return (x, y) except: self.set_coordinate((0, 0)) @@ -175,8 +171,8 @@ class Block(Element, _Block): rotation = eval(self.get_param('_rotation').get_value()) return int(rotation) except: - self.set_rotation(POSSIBLE_ROTATIONS[0]) - return POSSIBLE_ROTATIONS[0] + self.set_rotation(Constants.POSSIBLE_ROTATIONS[0]) + return Constants.POSSIBLE_ROTATIONS[0] def set_rotation(self, rot): """ @@ -204,23 +200,24 @@ class Block(Element, _Block): #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=BLOCK_FONT)) + layout.set_markup(Utils.parse_template(BLOCK_MARKUP_TMPL, block=self, font=Constants.BLOCK_FONT)) self.label_width, self.label_height = layout.get_pixel_size() #display the params if self.is_dummy_block: markups = [ - '<span foreground="black" font_desc="{font}"><b>key: </b>{key}</span>'.format(font=PARAM_FONT, key=self._key) + '<span foreground="black" font_desc="{font}"><b>key: </b>{key}</span>' + ''.format(font=Constants.PARAM_FONT, key=self._key) ] else: markups = [param.get_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(LABEL_SEPARATION*pango.SCALE) + layout.set_spacing(Constants.LABEL_SEPARATION * pango.SCALE) layout.set_markup('\n'.join(markups)) layouts.append(layout) w, h = layout.get_pixel_size() self.label_width = max(w, self.label_width) - self.label_height += h + LABEL_SEPARATION + self.label_height += h + Constants.LABEL_SEPARATION width = self.label_width height = self.label_height #setup the pixmap @@ -235,31 +232,31 @@ class Block(Element, _Block): 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 + LABEL_SEPARATION + 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) #calculate width and height needed - W = self.label_width + 2 * BLOCK_LABEL_PADDING + W = self.label_width + 2 * Constants.BLOCK_LABEL_PADDING def get_min_height_for_ports(): visible_ports = filter(lambda p: not p.get_hide(), ports) - min_height = 2*PORT_BORDER_SEPARATION + len(visible_ports) * PORT_SEPARATION + min_height = 2*Constants.PORT_BORDER_SEPARATION + len(visible_ports) * Constants.PORT_SEPARATION if visible_ports: min_height -= ports[0].H return min_height H = max( [ # labels - self.label_height + 2 * BLOCK_LABEL_PADDING + self.label_height + 2 * Constants.BLOCK_LABEL_PADDING ] + [ # ports get_min_height_for_ports() for ports in (self.get_sources_gui(), self.get_sinks_gui()) ] + [ # bus ports only - 2 * PORT_BORDER_SEPARATION + - sum([port.H + PORT_SPACING for port in ports if port.get_type() == 'bus']) - PORT_SPACING + 2 * Constants.PORT_BORDER_SEPARATION + + sum([port.H + Constants.PORT_SPACING for port in ports if port.get_type() == 'bus']) - Constants.PORT_SPACING for ports in (self.get_sources_gui(), self.get_sinks_gui()) ] ) @@ -284,12 +281,12 @@ class Block(Element, _Block): block=self, comment=comment, complexity=complexity, - font=BLOCK_FONT)) + font=Constants.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 + padding = Constants.BLOCK_LABEL_PADDING pixmap = self.get_parent().new_pixmap(width + 2 * padding, height + 2 * padding) gc = pixmap.new_gc() @@ -321,9 +318,9 @@ class Block(Element, _Block): ) #draw label image if self.is_horizontal(): - window.draw_drawable(gc, self.horizontal_label, 0, 0, x+BLOCK_LABEL_PADDING, y+(self.H-self.label_height)/2, -1, -1) + window.draw_drawable(gc, self.horizontal_label, 0, 0, x+Constants.BLOCK_LABEL_PADDING, y+(self.H-self.label_height)/2, -1, -1) elif self.is_vertical(): - window.draw_drawable(gc, self.vertical_label, 0, 0, x+(self.H-self.label_height)/2, y+BLOCK_LABEL_PADDING, -1, -1) + window.draw_drawable(gc, self.vertical_label, 0, 0, x+(self.H-self.label_height)/2, y+Constants.BLOCK_LABEL_PADDING, -1, -1) def what_is_selected(self, coor, coor_m=None): """ @@ -345,6 +342,10 @@ class Block(Element, _Block): if not self._comment_pixmap: return x, y = self.get_coordinate() - y += self.H if self.is_horizontal() else self.W - window.draw_drawable(gc, self._comment_pixmap, 0, 0, x, - y + BLOCK_LABEL_PADDING, -1, -1) + + if self.is_horizontal(): + y += self.H + Constants.BLOCK_LABEL_PADDING + else: + x += self.H + Constants.BLOCK_LABEL_PADDING + + window.draw_drawable(gc, self._comment_pixmap, 0, 0, x, y, -1, -1) diff --git a/grc/gui/Colors.py b/grc/gui/Colors.py index 050b363cdd..d322afa410 100644 --- a/grc/gui/Colors.py +++ b/grc/gui/Colors.py @@ -39,7 +39,7 @@ try: #block color constants BLOCK_ENABLED_COLOR = get_color('#F1ECFF') BLOCK_DISABLED_COLOR = get_color('#CCCCCC') - BLOCK_BYPASSED_COLOR = get_color('#FFFFE6') + BLOCK_BYPASSED_COLOR = get_color('#F4FF81') #connection color constants CONNECTION_ENABLED_COLOR = get_color('black') CONNECTION_DISABLED_COLOR = get_color('#BBBBBB') diff --git a/grc/gui/Constants.py b/grc/gui/Constants.py index e267c6ca02..022564cd77 100644 --- a/grc/gui/Constants.py +++ b/grc/gui/Constants.py @@ -40,7 +40,7 @@ MIN_DIALOG_WIDTH = 500 MIN_DIALOG_HEIGHT = 500 # default sizes DEFAULT_BLOCKS_WINDOW_WIDTH = 100 -DEFAULT_REPORTS_WINDOW_WIDTH = 100 +DEFAULT_CONSOLE_WINDOW_WIDTH = 100 DEFAULT_CANVAS_SIZE_DEFAULT = 1280, 1024 diff --git a/grc/gui/Dialogs.py b/grc/gui/Dialogs.py index 6cfdd50a34..1d114356c8 100644 --- a/grc/gui/Dialogs.py +++ b/grc/gui/Dialogs.py @@ -92,21 +92,21 @@ class TextDisplay(SimpleTextDisplay): buffer.delete(buffer.get_start_iter(), buffer.get_end_iter()) def save(self, file_path): - report_file = open(file_path, 'w') + console_file = open(file_path, 'w') buffer = self.get_buffer() - report_file.write(buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), True)) - report_file.close() + console_file.write(buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), True)) + console_file.close() # Callback functions to handle the scrolling lock and clear context menus options # Action functions are set by the ActionHandler's init function def clear_cb(self, menu_item, web_view): - Actions.CLEAR_REPORTS() + Actions.CLEAR_CONSOLE() def scroll_back_cb(self, menu_item, web_view): Actions.TOGGLE_SCROLL_LOCK() def save_cb(self, menu_item, web_view): - Actions.SAVE_REPORTS() + Actions.SAVE_CONSOLE() def populate_popup(self, view, menu): """Create a popup menu for the scroll lock and clear functions""" diff --git a/grc/gui/Executor.py b/grc/gui/Executor.py index f75f514cdb..bf9eecb9a8 100644 --- a/grc/gui/Executor.py +++ b/grc/gui/Executor.py @@ -30,7 +30,7 @@ from ..core import Messages class ExecFlowGraphThread(threading.Thread): """Execute the flow graph as a new process and wait on it to finish.""" - def __init__(self, action_handler): + def __init__(self, flow_graph_page, xterm_executable, callback): """ ExecFlowGraphThread constructor. @@ -38,19 +38,17 @@ class ExecFlowGraphThread(threading.Thread): action_handler: an instance of an ActionHandler """ threading.Thread.__init__(self) - self.update_exec_stop = action_handler.update_exec_stop - self.flow_graph = action_handler.get_flow_graph() - self.xterm_executable = action_handler.platform.config.xterm_executable - #store page and dont use main window calls in run - self.page = action_handler.get_page() - #get the popen + + self.page = flow_graph_page # store page and dont use main window calls in run + self.xterm_executable = xterm_executable + self.update_callback = callback + try: - self.p = self._popen() - self.page.set_proc(self.p) - #update - self.update_exec_stop() + self.process = self._popen() + self.page.set_proc(self.process) + self.update_callback() self.start() - except Exception, e: + except Exception as e: Messages.send_verbose_exec(str(e)) Messages.send_end_exec() @@ -58,7 +56,7 @@ class ExecFlowGraphThread(threading.Thread): """ Execute this python flow graph. """ - run_command = self.flow_graph.get_option('run_command') + run_command = self.page.get_flow_graph().get_option('run_command') generator = self.page.get_generator() try: @@ -90,19 +88,19 @@ class ExecFlowGraphThread(threading.Thread): Wait on the executing process by reading from its stdout. Use gobject.idle_add when calling functions that modify gtk objects. """ - #handle completion + # handle completion r = "\n" while r: gobject.idle_add(Messages.send_verbose_exec, r) - r = os.read(self.p.stdout.fileno(), 1024) - self.p.poll() + r = os.read(self.process.stdout.fileno(), 1024) + self.process.poll() gobject.idle_add(self.done) def done(self): """Perform end of execution tasks.""" - Messages.send_end_exec(self.p.returncode) + Messages.send_end_exec(self.process.returncode) self.page.set_proc(None) - self.update_exec_stop() + self.update_callback() ########################################################### diff --git a/grc/gui/FileDialogs.py b/grc/gui/FileDialogs.py index 4b5770ad21..e9430b1f88 100644 --- a/grc/gui/FileDialogs.py +++ b/grc/gui/FileDialogs.py @@ -33,7 +33,7 @@ import Utils ################################################## OPEN_FLOW_GRAPH = 'open flow graph' SAVE_FLOW_GRAPH = 'save flow graph' -SAVE_REPORTS = 'save reports' +SAVE_CONSOLE = 'save console' SAVE_IMAGE = 'save image' OPEN_QSS_THEME = 'open qss theme' @@ -43,46 +43,45 @@ File <b>$encode($filename)</b> Exists!\nWould you like to overwrite the existing FILE_DNE_MARKUP_TMPL="""\ File <b>$encode($filename)</b> Does not Exist!""" -################################################## + + # File Filters -################################################## -##the filter for flow graph files def get_flow_graph_files_filter(): 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.set_name('Text Files') filter.add_pattern('*'+TEXT_FILE_EXTENSION) return filter -##the filter for image files + def get_image_files_filter(): filter = gtk.FileFilter() filter.set_name('Image Files') filter.add_pattern('*'+IMAGE_FILE_EXTENSION) return filter -##the filter for all files + def get_all_files_filter(): filter = gtk.FileFilter() filter.set_name('All Files') 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 -################################################## class FileDialogHelper(gtk.FileChooserDialog): """ A wrapper class for the gtk file chooser dialog. @@ -105,6 +104,7 @@ class FileDialogHelper(gtk.FileChooserDialog): self.set_local_only(True) self.add_filter(get_all_files_filter()) + class FileDialog(FileDialogHelper): """A dialog box to save or open flow graph files. This is a base class, do not use.""" @@ -124,8 +124,8 @@ class FileDialog(FileDialogHelper): FileDialogHelper.__init__(self, gtk.FILE_CHOOSER_ACTION_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_REPORTS: - FileDialogHelper.__init__(self, gtk.FILE_CHOOSER_ACTION_SAVE, 'Save Reports to a File...') + elif self.type == SAVE_CONSOLE: + FileDialogHelper.__init__(self, gtk.FILE_CHOOSER_ACTION_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 @@ -164,11 +164,11 @@ class FileDialog(FileDialogHelper): ############################################# # Handle Save Dialogs ############################################# - if self.type in (SAVE_FLOW_GRAPH, SAVE_REPORTS, SAVE_IMAGE): + if self.type in (SAVE_FLOW_GRAPH, SAVE_CONSOLE, SAVE_IMAGE): filename = self.get_filename() extension = { SAVE_FLOW_GRAPH: Preferences.file_extension(), - SAVE_REPORTS: TEXT_FILE_EXTENSION, + SAVE_CONSOLE: TEXT_FILE_EXTENSION, SAVE_IMAGE: IMAGE_FILE_EXTENSION, }[self.type] #append the missing file extension if the filter matches @@ -205,11 +205,25 @@ class FileDialog(FileDialogHelper): self.destroy() return filename -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 + +class OpenFlowGraphFileDialog(FileDialog): + type = OPEN_FLOW_GRAPH + + +class SaveFlowGraphFileDialog(FileDialog): + type = SAVE_FLOW_GRAPH + + +class OpenQSSFileDialog(FileDialog): + type = OPEN_QSS_THEME + + +class SaveConsoleFileDialog(FileDialog): + type = SAVE_CONSOLE + + +class SaveImageFileDialog(FileDialog): + type = SAVE_IMAGE class SaveScreenShotDialog(SaveImageFileDialog): diff --git a/grc/gui/FlowGraph.py b/grc/gui/FlowGraph.py index 357f87c894..02d5197fb0 100644 --- a/grc/gui/FlowGraph.py +++ b/grc/gui/FlowGraph.py @@ -312,6 +312,47 @@ class FlowGraph(Element, _Flowgraph): selected_block.move(delta_coordinate) self.element_moved = True + def align_selected(self, calling_action=None): + """ + Align the selected blocks. + + Args: + calling_action: the action initiating the alignment + + Returns: + True if changed, otherwise False + """ + blocks = self.get_selected_blocks() + if calling_action is None or not blocks: + return False + + # compute common boundary of selected objects + min_x, min_y = max_x, max_y = blocks[0].get_coordinate() + for selected_block in blocks: + x, y = selected_block.get_coordinate() + min_x, min_y = min(min_x, x), min(min_y, y) + x += selected_block.W + y += selected_block.H + max_x, max_y = max(max_x, x), max(max_y, y) + ctr_x, ctr_y = (max_x + min_x)/2, (max_y + min_y)/2 + + # align the blocks as requested + transform = { + Actions.BLOCK_VALIGN_TOP: lambda x, y, w, h: (x, min_y), + Actions.BLOCK_VALIGN_MIDDLE: lambda x, y, w, h: (x, ctr_y - h/2), + Actions.BLOCK_VALIGN_BOTTOM: lambda x, y, w, h: (x, max_y - h), + Actions.BLOCK_HALIGN_LEFT: lambda x, y, w, h: (min_x, y), + Actions.BLOCK_HALIGN_CENTER: lambda x, y, w, h: (ctr_x-w/2, y), + Actions.BLOCK_HALIGN_RIGHT: lambda x, y, w, h: (max_x - w, y), + }.get(calling_action, lambda *args: args) + + for selected_block in blocks: + x, y = selected_block.get_coordinate() + w, h = selected_block.W, selected_block.H + selected_block.set_coordinate(transform(x, y, w, h)) + + return True + def rotate_selected(self, rotation): """ Rotate the selected blocks by multiples of 90 degrees. @@ -322,7 +363,8 @@ class FlowGraph(Element, _Flowgraph): Returns: true if changed, otherwise false. """ - if not self.get_selected_blocks(): return False + if not self.get_selected_blocks(): + return False #initialize min and max coordinates min_x, min_y = self.get_selected_block().get_coordinate() max_x, max_y = self.get_selected_block().get_coordinate() @@ -387,10 +429,14 @@ class FlowGraph(Element, _Flowgraph): window.draw_rectangle(gc, False, x, y, w, h) #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 for selected_element in self.get_selected_connections() + self.get_selected_blocks(): @@ -474,15 +520,15 @@ class FlowGraph(Element, _Flowgraph): selected_port = None selected = set() #check the elements + hide_disabled_blocks = Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active() + hide_variables = Actions.TOGGLE_HIDE_VARIABLES.get_active() for element in reversed(self.get_elements()): + 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 selected_element = element.what_is_selected(coor, coor_m) - if not selected_element: continue - # hidden disabled connections, blocks and their ports can not be selected - if Actions.TOGGLE_HIDE_DISABLED_BLOCKS.get_active() and ( - selected_element.is_block and not selected_element.get_enabled() or - selected_element.is_connection and not selected_element.get_enabled() or - selected_element.is_port and not selected_element.get_parent().get_enabled() - ): + if not selected_element: continue #update the selected port information if selected_element.is_port: @@ -533,7 +579,8 @@ class FlowGraph(Element, _Flowgraph): Returns: a block or None """ - return self.get_selected_blocks() and self.get_selected_blocks()[0] or None + selected_blocks = self.get_selected_blocks() + return selected_blocks[0] if selected_blocks else None def get_selected_elements(self): """ @@ -551,7 +598,8 @@ class FlowGraph(Element, _Flowgraph): Returns: a block, port, or connection or None """ - return self.get_selected_elements() and self.get_selected_elements()[0] or None + selected_elements = self.get_selected_elements() + return selected_elements[0] if selected_elements else None def update_selected_elements(self): """ diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index 6da240d85b..1437391236 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -23,8 +23,9 @@ import gtk from . import Bars, Actions, Preferences, Utils from .BlockTreeWindow import BlockTreeWindow +from .VariableEditor import VariableEditor from .Constants import \ - NEW_FLOGRAPH_TITLE, DEFAULT_REPORTS_WINDOW_WIDTH + NEW_FLOGRAPH_TITLE, DEFAULT_CONSOLE_WINDOW_WIDTH from .Dialogs import TextDisplay, MessageDialogHelper from .NotebookPage import NotebookPage @@ -56,6 +57,7 @@ PAGE_TITLE_MARKUP_TMPL = """\ #end if """ + ############################################################ # Main window ############################################################ @@ -63,10 +65,15 @@ PAGE_TITLE_MARKUP_TMPL = """\ 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. + BLOCKS = 0 + CONSOLE = 1 + VARIABLES = 2 + def __init__(self, platform, action_handler_callback): """ - MainWindow contructor - Setup the menu, toolbar, flowgraph editor notebook, block selection window... + MainWindow constructor + Setup the menu, toolbar, flow graph editor notebook, block selection window... """ self._platform = platform @@ -76,48 +83,80 @@ class MainWindow(gtk.Window): (o.get_key(), o.get_name(), o.get_key() == generate_mode_default) for o in gen_opts.get_options()] - # load preferences + # Load preferences Preferences.load(platform) - #setup window + + # Setup window gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) vbox = gtk.VBox() - self.hpaned = gtk.HPaned() self.add(vbox) - #create the menu bar and toolbar + + # 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) - self.tool_bar = Bars.Toolbar(generate_modes, action_handler_callback ) + self.tool_bar = Bars.Toolbar(generate_modes, action_handler_callback) vbox.pack_start(self.tool_bar, False) - vbox.pack_start(self.hpaned) - #create the notebook + + # Main parent container for the different panels + self.container = gtk.HPaned() + vbox.pack_start(self.container) + + # Create the notebook self.notebook = gtk.Notebook() self.page_to_be_closed = None self.current_page = None self.notebook.set_show_border(False) - self.notebook.set_scrollable(True) #scroll arrows for page tabs + self.notebook.set_scrollable(True) # scroll arrows for page tabs self.notebook.connect('switch-page', self._handle_page_change) - #setup containers - self.flow_graph_vpaned = gtk.VPaned() - #flow_graph_box.pack_start(self.scrolled_window) - self.flow_graph_vpaned.pack1(self.notebook) - self.hpaned.pack1(self.flow_graph_vpaned) - self.btwin = BlockTreeWindow(platform, self.get_flow_graph); - self.hpaned.pack2(self.btwin, False) #dont allow resize - #create the reports window + + # Create the console window self.text_display = TextDisplay() - #house the reports in a scrolled window - self.reports_scrolled_window = gtk.ScrolledWindow() - self.reports_scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - self.reports_scrolled_window.add(self.text_display) - self.reports_scrolled_window.set_size_request(-1, DEFAULT_REPORTS_WINDOW_WIDTH) - self.flow_graph_vpaned.pack2(self.reports_scrolled_window, False) #dont allow resize - #load preferences and show the main window + self.console_window = gtk.ScrolledWindow() + self.console_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_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) + + # Figure out which place to put the variable editor + self.left = gtk.VPaned() + self.right = gtk.VPaned() + self.left_subpanel = gtk.HPaned() + + self.variable_panel_sidebar = Preferences.variable_editor_sidebar() + if self.variable_panel_sidebar: + self.left.pack1(self.notebook) + self.left.pack2(self.console_window, False) + self.right.pack1(self.btwin) + self.right.pack2(self.vars, False) + else: + # Put the variable editor in a panel with the console + self.left.pack1(self.notebook) + self.left_subpanel.pack1(self.console_window, shrink=False) + self.left_subpanel.pack2(self.vars, resize=False, shrink=True) + self.left.pack2(self.left_subpanel, False) + + # Create the right panel + self.right.pack1(self.btwin) + + self.container.pack1(self.left) + self.container.pack2(self.right, False) + + # load preferences and show the main window self.resize(*Preferences.main_window_size()) - self.flow_graph_vpaned.set_position(Preferences.reports_window_position()) - self.hpaned.set_position(Preferences.blocks_window_position()) + self.container.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)) + else: + self.left_subpanel.set_position(Preferences.variable_editor_position()) + self.show_all() - self.reports_scrolled_window.hide() + self.console_window.hide() + self.vars.hide() self.btwin.hide() ############################################################ @@ -148,14 +187,45 @@ class MainWindow(gtk.Window): page_num: new page number """ self.current_page = self.notebook.get_nth_page(page_num) - Messages.send_page_switch(self.current_page.get_file_path()) Actions.PAGE_CHANGE() + def update_panel_visibility(self, panel, visibility=True): + """ + Handles changing visibility of panels. + """ + # Set the visibility for the requested panel, then update the containers if they need + # to be hidden as well. + + if panel == self.BLOCKS: + self.btwin.set_visible(visibility) + elif panel == self.CONSOLE: + self.console_window.set_visible(visibility) + elif panel == self.VARIABLES: + self.vars.set_visible(visibility) + else: + return + + if self.variable_panel_sidebar: + # If both the variable editor and block panels are hidden, hide the right container + if not self.btwin.get_visible() and not self.vars.get_visible(): + self.right.hide() + else: + self.right.show() + else: + if not self.btwin.get_visible(): + self.right.hide() + else: + self.right.show() + if not self.vars.get_visible() and not self.console_window.get_visible(): + self.left_subpanel.hide() + else: + self.left_subpanel.show() + ############################################################ - # Report Window + # Console Window ############################################################ - def add_report_line(self, line): + def add_console_line(self, line): """ Place line at the end of the text buffer, then scroll its window all the way down. @@ -227,8 +297,12 @@ class MainWindow(gtk.Window): Preferences.set_open_files(open_files) Preferences.file_open(open_file) Preferences.main_window_size(self.get_size()) - Preferences.reports_window_position(self.flow_graph_vpaned.get_position()) - Preferences.blocks_window_position(self.hpaned.get_position()) + Preferences.console_window_position(self.left.get_position()) + Preferences.blocks_window_position(self.container.get_position()) + if self.variable_panel_sidebar: + Preferences.variable_editor_position(self.right.get_position(), sidebar=True) + else: + Preferences.variable_editor_position(self.left_subpanel.get_position()) Preferences.save() return True @@ -272,7 +346,7 @@ class MainWindow(gtk.Window): """ Set the title of the main window. Set the titles on the page tabs. - Show/hide the reports window. + Show/hide the console window. Args: title: the window title @@ -298,6 +372,9 @@ class MainWindow(gtk.Window): #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() + def update_pages(self): """ Forces a reload of all the pages in this notebook. diff --git a/grc/gui/NotebookPage.py b/grc/gui/NotebookPage.py index 6614649c89..c9e8d0f186 100644 --- a/grc/gui/NotebookPage.py +++ b/grc/gui/NotebookPage.py @@ -39,13 +39,13 @@ class NotebookPage(gtk.HBox): file_path: path to a flow graph file """ self._flow_graph = flow_graph - self.set_proc(None) + self.process = None #import the file self.main_window = main_window - self.set_file_path(file_path) + self.file_path = file_path initial_state = flow_graph.get_parent().parse_flow_graph(file_path) self.state_cache = StateCache(initial_state) - self.set_saved(True) + self.saved = True #import the data to the flow graph self.get_flow_graph().import_data(initial_state) #initialize page gui @@ -189,8 +189,7 @@ class NotebookPage(gtk.HBox): Args: file_path: file path string """ - if file_path: self.file_path = os.path.abspath(file_path) - else: self.file_path = '' + self.file_path = os.path.abspath(file_path) if file_path else '' def get_saved(self): """ diff --git a/grc/gui/Param.py b/grc/gui/Param.py index bf0a59b96b..4b5a3c294a 100644 --- a/grc/gui/Param.py +++ b/grc/gui/Param.py @@ -84,7 +84,7 @@ class InputParam(gtk.HBox): self._have_pending_changes = True self._update_gui() if self._editing_callback: - self._editing_callback() + self._editing_callback(self, None) def _apply_change(self, *args): """ @@ -95,7 +95,7 @@ class InputParam(gtk.HBox): self.param.set_value(self.get_text()) #call the callback if self._changed_callback: - self._changed_callback(*args) + self._changed_callback(self, None) else: self.param.validate() #gui update @@ -129,8 +129,19 @@ class EntryParam(InputParam): return self._input.get_text() def set_color(self, color): - self._input.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(color)) - self._input.modify_text(gtk.STATE_NORMAL, Colors.PARAM_ENTRY_TEXT_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: diff --git a/grc/gui/Preferences.py b/grc/gui/Preferences.py index 1a194fd8c5..5fbdfe927a 100644 --- a/grc/gui/Preferences.py +++ b/grc/gui/Preferences.py @@ -140,14 +140,31 @@ def add_recent_file(file_name): set_recent_files(recent_files[:10]) # Keep up to 10 files -def reports_window_position(pos=None): - return entry('reports_window_position', pos, default=-1) or 1 +def console_window_position(pos=None): + return entry('console_window_position', pos, default=-1) or 1 def blocks_window_position(pos=None): return entry('blocks_window_position', pos, default=-1) or 1 +def variable_editor_position(pos=None, sidebar=False): + # Figure out default + if sidebar: + w, h = main_window_size() + return entry('variable_editor_sidebar_position', pos, default=int(h*0.7)) + else: + return entry('variable_editor_position', pos, default=int(blocks_window_position()*0.5)) + + +def variable_editor_sidebar(pos=None): + return entry('variable_editor_sidebar', pos, default=False) + + +def variable_editor_confirm_delete(pos=None): + return entry('variable_editor_confirm_delete', pos, default=True) + + def xterm_missing(cmd=None): return entry('xterm_missing', cmd, default='INVALID_XTERM_SETTING') diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py index f20e3c0fa6..51b9b19e9f 100644 --- a/grc/gui/Utils.py +++ b/grc/gui/Utils.py @@ -123,10 +123,11 @@ class TemplateParser(object): parse_template = TemplateParser() -def align_to_grid(coor): - _align = lambda: int(round(x / (1.0 * CANVAS_GRID_SIZE)) * CANVAS_GRID_SIZE) +def align_to_grid(coor, mode=round): + def align(value): + return int(mode(value / (1.0 * CANVAS_GRID_SIZE)) * CANVAS_GRID_SIZE) try: - return [_align() for x in coor] + return map(align, coor) except TypeError: x = coor - return _align() + return align(coor) diff --git a/grc/gui/VariableEditor.py b/grc/gui/VariableEditor.py new file mode 100644 index 0000000000..7721f3bda6 --- /dev/null +++ b/grc/gui/VariableEditor.py @@ -0,0 +1,354 @@ +""" +Copyright 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 operator import attrgetter + +import pygtk +pygtk.require('2.0') +import gtk +import gobject + +from . import Actions +from . import Preferences +from .Constants import DEFAULT_BLOCKS_WINDOW_WIDTH + +BLOCK_INDEX = 0 +ID_INDEX = 1 + + +class VariableEditorContextMenu(gtk.Menu): + """ A simple context menu for our variable editor """ + def __init__(self, var_edit): + gtk.Menu.__init__(self) + + 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.connect('activate', var_edit.handle_action, var_edit.ADD_VARIABLE) + self.add(self.variables) + self.add(gtk.SeparatorMenuItem()) + + self.enable = gtk.MenuItem("_Enable") + self.enable.connect('activate', var_edit.handle_action, var_edit.ENABLE_BLOCK) + 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.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.properties = gtk.MenuItem("_Properties...") + self.properties.connect('activate', var_edit.handle_action, var_edit.OPEN_PROPERTIES) + self.add(self.properties) + self.show_all() + + def update_sensitive(self, selected, enabled=False): + self.delete.set_sensitive(selected) + self.properties.set_sensitive(selected) + self.enable.set_sensitive(selected and not enabled) + self.disable.set_sensitive(selected and enabled) + + +class VariableEditor(gtk.VBox): + + # Actions that are handled by the editor + ADD_IMPORT = 0 + ADD_VARIABLE = 1 + OPEN_PROPERTIES = 2 + DELETE_BLOCK = 3 + DELETE_CONFIRM = 4 + 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 + self._block = None + self._mouse_button_pressed = False + + # 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.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.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) + self.treeview.connect('motion-notify-event', self._handle_motion_notify) + self.treeview.connect('key-press-event', self._handle_key_button_press) + + # Block Name or Category + 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.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_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) + # For forcing resize + self._col_width = 0 + + # Block Value + 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.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_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() + 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.add_with_viewport(self.treeview) + scrolled_window.set_size_request(DEFAULT_BLOCKS_WINDOW_WIDTH, -1) + self.pack_start(scrolled_window) + + # 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): + 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) + + def set_value(self, col, cell, model, iter): + 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) + + # Set defaults + value = None + self.set_tooltip_text(None) + + # Block specific values + if block: + if block.get_key() == 'import': + value = block.get_param('import').get_value() + elif block.get_key() != "variable": + value = "<Open Properties>" + sp('editable', False) + sp('foreground', '#0D47A1') + else: + value = block.get_param('value').get_value() + + # Check if there are errors in the blocks. + # Show the block error as a tooltip + error_message = block.get_error_messages() + if len(error_message) > 0: + # Set the error message to the last error in the list. + # This should be the first message generated + self.set_tooltip_text(error_message[-1]) + else: + # Evaluate and show the value (if it is a variable) + if block.get_key() == "variable": + evaluated = str(block.get_param('value').evaluate()) + self.set_tooltip_text(evaluated) + # Always set the text value. + sp('text', value) + + def set_properties(self, col, cell, model, iter): + sp = cell.set_property + block = model.get_value(iter, BLOCK_INDEX) + # Set defaults + sp('sensitive', True) + sp('editable', False) + sp('foreground', None) + + # Block specific changes + if block: + if not block.get_enabled(): + # Disabled block. But, this should still be editable + sp('editable', True) + sp('foreground', 'gray') + else: + sp('editable', True) + if block.get_error_messages(): + sp('foreground', 'red') + + def update_gui(self): + if not self.get_flow_graph(): + return + self._update_blocks() + 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']) + variables = self.treestore.append(None, [None, 'Variables']) + for block in self._imports: + self.treestore.append(imports, [block, block.get_param('id').get_value()]) + for block in sorted(self._variables, key=lambda v: v.get_id()): + self.treestore.append(variables, [block, block.get_param('id').get_value()]) + + def _handle_name_edited_cb(self, cell, path, new_text): + block = self.treestore[path][BLOCK_INDEX] + block.get_param('id').set_value(new_text) + Actions.VARIABLE_EDITOR_UPDATE() + + def _handle_value_edited_cb(self, cell, path, new_text): + block = self.treestore[path][BLOCK_INDEX] + if block.is_import: + block.get_param('import').set_value(new_text) + else: + block.get_param('value').set_value(new_text) + Actions.VARIABLE_EDITOR_UPDATE() + + def handle_action(self, item, key, event=None): + """ + Single handler for the different actions that can be triggered by the context menu, + 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') + elif key == self.ADD_VARIABLE: + self.get_flow_graph().add_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) + elif key == self.DELETE_CONFIRM: + if self._confirm_delete: + # Create a context menu to confirm the delete operation + confirmation_menu = gtk.Menu() + block_id = self._block.get_param('id').get_value().replace("_", "__") + confirm = gtk.MenuItem("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) + else: + self.handle_action(None, self.DELETE_BLOCK, None) + elif key == self.ENABLE_BLOCK: + self._block.set_enabled(True) + elif key == self.DISABLE_BLOCK: + self._block.set_enabled(False) + Actions.VARIABLE_EDITOR_UPDATE() + + def _handle_mouse_button_press(self, widget, event): + """ + Handles mouse button for several different events: + - Double Click to open properties for advanced blocks + - Click to add/remove blocks + """ + # Save the column width to see if it changes on button_release + self._mouse_button_pressed = True + self._col_width = self.id_column.get_width() + + path = widget.get_path_at_pos(int(event.x), int(event.y)) + if path: + # If there is a valid path, then get the row, column and block selected. + row = self.treestore[path[0]] + col = path[1] + self._block = row[BLOCK_INDEX] + + 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: + # 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: + # 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]: + if row[1] == "Imports": + # Add a new import block. + self.handle_action(None, self.ADD_IMPORT, event=event) + elif row[1] == "Variables": + # Add a new variable block + self.handle_action(None, self.ADD_VARIABLE, event=event) + else: + self.handle_action(None, self.DELETE_CONFIRM, event=event) + return True + elif event.button == 3 and event.type == gtk.gdk.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) + + # Null handler. Stops the treeview from handling double click events. + if event.type == gtk.gdk._2BUTTON_PRESS: + return True + return False + + def _handle_mouse_button_release(self, widget, event): + self._mouse_button_pressed = False + return False + + def _handle_motion_notify(self, widget, event): + # Check to see if the column size has changed + if self._mouse_button_pressed and self.id_column.get_width() != self._col_width: + self.value_column.queue_resize() + return False + + def _handle_key_button_press(self, widget, event): + model, path = self.treeview.get_selection().get_selected_rows() + if path and self._block: + if self._block.get_enabled() and event.string == "d": + self.handle_action(None, self.DISABLE_BLOCK, None) + return True + elif not self._block.get_enabled() and event.string == "e": + self.handle_action(None, self.ENABLE_BLOCK, None) + return True + return False |