Repository: slaclab/pydm (language: python)

Issue:
ENH: Write to NTTable subfield 
# Description
Adds the ability for a PyDMWritableWidget which have a sub-field of a NTTable as there channel NTTable to write to the sub-field. 

Address a bug in passing a NTTable with boolean data. #988

Changes the calc and local plugins to match the new way PyDM parses the channel address. 

## Motivation and Context
Provide a follow up to clean up and expand the new NTTable introduced in v.1.19.0 

## How Has This Been Tested?
Tested on an example Gui.

## Where Has This Been Documented?
new methods have been given docstrings which will be automatically added to the documentation.  

## Screenshots:


Discussion / hints:
## [Codecov](https://app.codecov.io/gh/slaclab/pydm/pull/992?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab) Report
> Merging [#992](https://app.codecov.io/gh/slaclab/pydm/pull/992?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab) (77aceef) into [master](https://app.codecov.io/gh/slaclab/pydm/commit/ec38688e196f5dda5254e9cda8859f43eaa96432?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab) (ec38688) will **increase** coverage by `0.00%`.
> The diff coverage is `70.83%`.

:mega: This organization is not using Codecov’s [GitHub App Integration](https://github.com/apps/codecov). We recommend you install it so Codecov can continue to function properly for your repositories. [Learn more](https://about.codecov.io/blog/codecov-is-updating-its-github-integration/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab)

```diff
@@           Coverage Diff           @@
##           master     #992   +/-   ##
=======================================
  Coverage   59.21%   59.21%           
=======================================
  Files         105      105           
  Lines       14000    14036   +36     
=======================================
+ Hits         8290     8312   +22     
- Misses       5710     5724   +14     
```


| [Impacted Files](https://app.codecov.io/gh/slaclab/pydm/pull/992?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab) | Coverage Δ | |
|---|---|---|
| [pydm/data\_plugins/calc\_plugin.py](https://app.codecov.io/gh/slaclab/pydm/pull/992?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab#diff-cHlkbS9kYXRhX3BsdWdpbnMvY2FsY19wbHVnaW4ucHk=) | `71.89% <50.00%> (ø)` | |
| [...data\_plugins/epics\_plugins/p4p\_plugin\_component.py](https://app.codecov.io/gh/slaclab/pydm/pull/992?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab#diff-cHlkbS9kYXRhX3BsdWdpbnMvZXBpY3NfcGx1Z2lucy9wNHBfcGx1Z2luX2NvbXBvbmVudC5weQ==) | `57.34% <65.62%> (+1.16%)` | :arrow_up: |
| [pydm/data\_plugins/local\_plugin.py](https://app.codecov.io/gh/slaclab/pydm/pull/992?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab#diff-cHlkbS9kYXRhX3BsdWdpbnMvbG9jYWxfcGx1Z2luLnB5) | `66.38% <100.00%> (-0.15%)` | :arrow_down: |
| [pydm/data\_plugins/plugin.py](https://app.codecov.io/gh/slaclab/pydm/pull/992?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab#diff-cHlkbS9kYXRhX3BsdWdpbnMvcGx1Z2luLnB5) | `78.42% <100.00%> (-0.33%)` | :arrow_down: |
| [pydm/utilities/remove\_protocol.py](https://app.codecov.io/gh/slaclab/pydm/pull/992?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab#diff-cHlkbS91dGlsaXRpZXMvcmVtb3ZlX3Byb3RvY29sLnB5) | `100.00% <100.00%> (ø)` | |

... and [3 files with indirect coverage changes](https://app.codecov.io/gh/slaclab/pydm/pull/992/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slaclab)



Fix patch:
diff --git a/examples/pva/nt_table.ui b/examples/pva/nt_table.ui
new file mode 100644
index 000000000..e00abd2d1
--- /dev/null
+++ b/examples/pva/nt_table.ui
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Form</class>
+ <widget class="QWidget" name="Form">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>356</width>
+    <height>419</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <widget class="PyDMNTTable" name="PyDMNTTable">
+   <property name="geometry">
+    <rect>
+     <x>10</x>
+     <y>20</y>
+     <width>331</width>
+     <height>171</height>
+    </rect>
+   </property>
+   <property name="toolTip">
+    <string/>
+   </property>
+   <property name="alarmSensitiveContent" stdset="0">
+    <bool>false</bool>
+   </property>
+   <property name="alarmSensitiveBorder" stdset="0">
+    <bool>true</bool>
+   </property>
+   <property name="PyDMToolTip" stdset="0">
+    <string/>
+   </property>
+   <property name="channel" stdset="0">
+    <string>pva://PyDM:PVA:Table</string>
+   </property>
+   <property name="readOnly" stdset="0">
+    <bool>true</bool>
+   </property>
+  </widget>
+  <widget class="PyDMLabel" name="PyDMLabel">
+   <property name="geometry">
+    <rect>
+     <x>30</x>
+     <y>240</y>
+     <width>231</width>
+     <height>31</height>
+    </rect>
+   </property>
+   <property name="toolTip">
+    <string/>
+   </property>
+   <property name="precision" stdset="0">
+    <number>0</number>
+   </property>
+   <property name="showUnits" stdset="0">
+    <bool>false</bool>
+   </property>
+   <property name="precisionFromPV" stdset="0">
+    <bool>true</bool>
+   </property>
+   <property name="alarmSensitiveContent" stdset="0">
+    <bool>false</bool>
+   </property>
+   <property name="alarmSensitiveBorder" stdset="0">
+    <bool>true</bool>
+   </property>
+   <property name="PyDMToolTip" stdset="0">
+    <string/>
+   </property>
+   <property name="channel" stdset="0">
+    <string>pva://PyDM:PVA:Table/names</string>
+   </property>
+  </widget>
+  <widget class="PyDMLabel" name="PyDMLabel_2">
+   <property name="geometry">
+    <rect>
+     <x>30</x>
+     <y>290</y>
+     <width>231</width>
+     <height>31</height>
+    </rect>
+   </property>
+   <property name="toolTip">
+    <string/>
+   </property>
+   <property name="precision" stdset="0">
+    <number>0</number>
+   </property>
+   <property name="showUnits" stdset="0">
+    <bool>false</bool>
+   </property>
+   <property name="precisionFromPV" stdset="0">
+    <bool>true</bool>
+   </property>
+   <property name="alarmSensitiveContent" stdset="0">
+    <bool>false</bool>
+   </property>
+   <property name="alarmSensitiveBorder" stdset="0">
+    <bool>true</bool>
+   </property>
+   <property name="PyDMToolTip" stdset="0">
+    <string/>
+   </property>
+   <property name="channel" stdset="0">
+    <string>pva://PyDM:PVA:Table/floats</string>
+   </property>
+  </widget>
+  <widget class="PyDMLabel" name="PyDMLabel_3">
+   <property name="geometry">
+    <rect>
+     <x>40</x>
+     <y>380</y>
+     <width>231</width>
+     <height>31</height>
+    </rect>
+   </property>
+   <property name="toolTip">
+    <string/>
+   </property>
+   <property name="precision" stdset="0">
+    <number>0</number>
+   </property>
+   <property name="showUnits" stdset="0">
+    <bool>false</bool>
+   </property>
+   <property name="precisionFromPV" stdset="0">
+    <bool>true</bool>
+   </property>
+   <property name="alarmSensitiveContent" stdset="0">
+    <bool>false</bool>
+   </property>
+   <property name="alarmSensitiveBorder" stdset="0">
+    <bool>true</bool>
+   </property>
+   <property name="PyDMToolTip" stdset="0">
+    <string/>
+   </property>
+   <property name="channel" stdset="0">
+    <string>pva://PyDM:PVA:Table/booleans/2</string>
+   </property>
+  </widget>
+  <widget class="QLabel" name="label">
+   <property name="geometry">
+    <rect>
+     <x>30</x>
+     <y>200</y>
+     <width>281</width>
+     <height>21</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Retrieving individual columns of the table:</string>
+   </property>
+  </widget>
+  <widget class="QLabel" name="label_4">
+   <property name="geometry">
+    <rect>
+     <x>30</x>
+     <y>360</y>
+     <width>331</width>
+     <height>21</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Retrieiving a specific cell from the table by index:</string>
+   </property>
+  </widget>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>PyDMNTTable</class>
+   <extends>QWidget</extends>
+   <header>pydm.widgets.nt_table</header>
+  </customwidget>
+  <customwidget>
+   <class>PyDMLabel</class>
+   <extends>QLabel</extends>
+   <header>pydm.widgets.label</header>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>


Test patch:
diff --git a/examples/testing_ioc/pva_testing_ioc.py b/examples/testing_ioc/pva_testing_ioc.py
index ee4da19b6..08ede7a06 100644
--- a/examples/testing_ioc/pva_testing_ioc.py
+++ b/examples/testing_ioc/pva_testing_ioc.py
@@ -1,5 +1,5 @@
 from p4p.client.thread import Context
-from p4p.nt import NTNDArray, NTScalar
+from p4p.nt import NTNDArray, NTScalar, NTTable
 from p4p.server import Server, ServerOperation
 from p4p.server.thread import SharedPV
 import numpy as np
@@ -55,6 +55,14 @@ def create_pvs(self) -> None:
         # An NTNDArray that will be used to hold image data
         self.image_pv = SharedPV(handler=handler, nt=NTNDArray(), initial=np.zeros(1))
 
+        # An NTTable that can be displayed via the PyDMNTTable widget
+        table_structure = NTTable([('names', 's'), ('floats', 'd'), ('booleans', '?')])
+        table_strings = ['This', 'Is', 'A', 'PyDM', 'Table']
+        table_rows = []
+        for i in range(5):
+            table_rows.append({'names': table_strings[i], 'floats': 0.35 * i, 'booleans': i % 2 == 0})
+        self.nt_table_pv = SharedPV(handler=handler, nt=table_structure, initial=table_structure.wrap(table_rows))
+
     def run_server(self) -> None:
         """ Run the server that will provide the PVs until keyboard interrupt """
         Server.forever(providers=[{'PyDM:PVA:IntValue': self.int_value,
@@ -69,7 +77,8 @@ def run_server(self) -> None:
                                    'PyDM:PVA:Waveform': self.wave_form,
                                    'PyDM:PVA:BoolArray': self.bool_array,
                                    'PyDM:PVA:StringArray': self.string_array,
-                                   'PyDM:PVA:Image': self.image_pv}])
+                                   'PyDM:PVA:Image': self.image_pv,
+                                   'PyDM:PVA:Table': self.nt_table_pv}])
 
     def gaussian_2d(self, x: float, y: float, x0: float, y0: float, xsig: float, ysig: float) -> np.ndarray:
         return np.exp(-0.5 * (((x - x0) / xsig) ** 2 + ((y - y0) / ysig) ** 2))