Repository: Qiskit/qiskit

Issue:
[text drawer] controlled gates with labels 
@ewinston raises the question on how to handle controlled gates with labels in https://github.com/Qiskit/qiskit-terra/pull/4262#pullrequestreview-402277795.

Considering this case:
```python
h=HGate(label='my h')
ch = h.control(label='my ch')
qc.append(h, [0])
qc.append(ch, [0, 1])
```

This is the current output: 
```
     ┌──────┐┌────────┐
q_0: ┤ my h ├┤0       ├
     └──────┘│  my ch │
q_1: ────────┤1       ├
             └────────┘
```

Possible solutions:
```
     ┌──────┐ my ch
q_0: ┤ my h ├───■─────
     └──────┘┌──┴───┐
q_1: ────────┤ my h ├─
             └──────┘
```
```
     ┌──────┐
q_0: ┤ my h ├───■─────────
     └──────┘┌──┴my ch───┐
q_1: ────────┤    my h   ├
             └───────────┘
```


Discussion:

Resolving patch:
diff --git a/qiskit/visualization/text.py b/qiskit/visualization/text.py
--- a/qiskit/visualization/text.py
+++ b/qiskit/visualization/text.py
@@ -399,10 +399,15 @@ class Bullet(DirectOnQuWire):
         bot:  │      │
     """
 
-    def __init__(self, top_connect="", bot_connect="", conditional=False):
+    def __init__(self, top_connect="", bot_connect="", conditional=False,
+                 label=None, bottom=False):
         super().__init__('■')
         self.top_connect = top_connect
         self.bot_connect = '│' if conditional else bot_connect
+        if label and bottom:
+            self.bot_connect = label
+        elif label:
+            self.top_connect = label
         self.mid_bck = '─'
 
 
@@ -416,10 +421,15 @@ class OpenBullet(DirectOnQuWire):
         bot:  │      │
     """
 
-    def __init__(self, top_connect="", bot_connect="", conditional=False):
+    def __init__(self, top_connect="", bot_connect="", conditional=False,
+                 label=None, bottom=False):
         super().__init__('o')
         self.top_connect = top_connect
         self.bot_connect = '│' if conditional else bot_connect
+        if label and bottom:
+            self.bot_connect = label
+        elif label:
+            self.top_connect = label
         self.mid_bck = '─'
 
 
@@ -668,6 +678,10 @@ def should_compress(self, top_line, bot_line):
         for top, bot in zip(top_line, bot_line):
             if top == '┴' and bot == '┬':
                 return False
+        for line in (bot_line, top_line):
+            no_spaces = line.replace(' ', '')
+            if len(no_spaces) > 0 and all(c.isalpha() or c.isnumeric() for c in no_spaces):
+                return False
         return True
 
     def draw_wires(self, wires):
@@ -803,7 +817,7 @@ def merge_lines(top, bot, icod="top"):
                 ret += "╫"
             elif topc in '║╫╬' and botc in " ":
                 ret += "║"
-            elif topc == '└' and botc == "┌":
+            elif topc == '└' and botc == "┌" and icod == 'top':
                 ret += "├"
             elif topc == '┘' and botc == "┐":
                 ret += "┤"
@@ -867,7 +881,7 @@ def controlled_wires(instruction, layer):
                 in_box.append(ctrl_qubit)
         return (top_box, bot_box, in_box, args_qubits)
 
-    def _set_ctrl_state(self, instruction, conditional):
+    def _set_ctrl_state(self, instruction, conditional, ctrl_label, bottom):
         """ Takes the ctrl_state from instruction and appends Bullet or OpenBullet
         to gates depending on whether the bit in ctrl_state is 1 or 0. Returns gates"""
 
@@ -877,9 +891,11 @@ def _set_ctrl_state(self, instruction, conditional):
         cstate = "{0:b}".format(instruction.op.ctrl_state).rjust(num_ctrl_qubits, '0')[::-1]
         for i in range(len(ctrl_qubits)):
             if cstate[i] == '1':
-                gates.append(Bullet(conditional=conditional))
+                gates.append(Bullet(conditional=conditional, label=ctrl_label,
+                                    bottom=bottom))
             else:
-                gates.append(OpenBullet(conditional=conditional))
+                gates.append(OpenBullet(conditional=conditional, label=ctrl_label,
+                                        bottom=bottom))
         return gates
 
     def _instruction_to_gate(self, instruction, layer):
@@ -888,7 +904,12 @@ def _instruction_to_gate(self, instruction, layer):
 
         current_cons = []
         connection_label = None
+        ctrl_label = None
+        box_label = None
         conditional = False
+        multi_qubit_instruction = len(instruction.qargs) >= 2 and not instruction.cargs
+        label_multibox = False
+        base_gate = getattr(instruction.op, 'base_gate', None)
 
         if instruction.condition is not None:
             # conditional
@@ -904,14 +925,22 @@ def add_connected_gate(instruction, gates, layer, current_cons):
                     layer.set_qubit(instruction.qargs[i], gate)
                     current_cons.append((actual_index, gate))
 
-        if len(instruction.qargs) >= 2 and \
-                not instruction.cargs and \
+        if multi_qubit_instruction and \
+                getattr(instruction.op, 'label', None) is not None and \
+                getattr(base_gate, 'label', None) is not None:
+            # If a multi qubit instruction has a label, and the base gate has a
+            # label, the label is applied to the bullet instead of the box.
+            ctrl_label = getattr(instruction.op, 'label', None)
+            box_label = getattr(base_gate, 'label', None)
+
+        elif multi_qubit_instruction and \
                 getattr(instruction.op, 'label', None) is not None:
             # If a multi qubit instruction has a label, it is a box
+            label_multibox = True
             layer._set_multibox(instruction.op.label, qubits=instruction.qargs,
                                 conditional=conditional)
 
-        elif isinstance(instruction.op, MeasureInstruction):
+        if isinstance(instruction.op, MeasureInstruction):
             gate = MeasureFrom()
             layer.set_qubit(instruction.qargs[0], gate)
             if self.cregbundle:
@@ -948,23 +977,25 @@ def add_connected_gate(instruction, gates, layer, current_cons):
                             BoxOnQuWire(TextDrawing.label_for_box(instruction),
                                         conditional=conditional))
 
-        elif isinstance(instruction.op, ControlledGate):
-            label = TextDrawing.label_for_box(instruction, controlled=True)
+        elif isinstance(instruction.op, ControlledGate) and not label_multibox:
+            label = box_label if box_label is not None \
+                else TextDrawing.label_for_box(instruction, controlled=True)
             params_array = TextDrawing.controlled_wires(instruction, layer)
             controlled_top, controlled_bot, controlled_edge, rest = params_array
-            gates = self._set_ctrl_state(instruction, conditional)
-            if instruction.op.base_gate.name == 'z':
+            gates = self._set_ctrl_state(instruction, conditional, ctrl_label,
+                                         bool(controlled_bot))
+            if base_gate.name == 'z':
                 # cz
                 gates.append(Bullet(conditional=conditional))
-            elif instruction.op.base_gate.name == 'u1':
+            elif base_gate.name == 'u1':
                 # cu1
                 connection_label = TextDrawing.params_for_label(instruction)[0]
                 gates.append(Bullet(conditional=conditional))
-            elif instruction.op.base_gate.name == 'swap':
+            elif base_gate.name == 'swap':
                 # cswap
                 gates += [Ex(conditional=conditional), Ex(conditional=conditional)]
                 add_connected_gate(instruction, gates, layer, current_cons)
-            elif instruction.op.base_gate.name == 'rzz':
+            elif base_gate.name == 'rzz':
                 # crzz
                 connection_label = "zz(%s)" % TextDrawing.params_for_label(instruction)[0]
                 gates += [Bullet(conditional=conditional), Bullet(conditional=conditional)]
@@ -978,7 +1009,7 @@ def add_connected_gate(instruction, gates, layer, current_cons):
                 for index in range(min(indexes), max(indexes) + 1):
                     # Dummy element to connect the multibox with the bullets
                     current_cons.append((index, DrawElement('')))
-            elif instruction.op.base_gate.name == 'z':
+            elif base_gate.name == 'z':
                 gates.append(Bullet(conditional=conditional))
             else:
                 gates.append(BoxOnQuWire(label, conditional=conditional))


Test patch:
diff --git a/test/python/visualization/test_circuit_text_drawer.py b/test/python/visualization/test_circuit_text_drawer.py
--- a/test/python/visualization/test_circuit_text_drawer.py
+++ b/test/python/visualization/test_circuit_text_drawer.py
@@ -1074,6 +1074,130 @@ def test_label_turns_to_box_2286(self):
 
         self.assertEqual(str(_text_circuit_drawer(circ)), expected)
 
+    def test_control_gate_with_base_label_4361(self):
+        """Control gate has a label and a base gate with a label
+        See https://github.com/Qiskit/qiskit-terra/issues/4361 """
+        expected = '\n'.join(["        ┌──────┐ my ch  ┌──────┐",
+                              "q_0: |0>┤ my h ├───■────┤ my h ├",
+                              "        └──────┘┌──┴───┐└──┬───┘",
+                              "q_1: |0>────────┤ my h ├───■────",
+                              "                └──────┘ my ch  "])
+        qr = QuantumRegister(2, 'q')
+        circ = QuantumCircuit(qr)
+        hgate = HGate(label='my h')
+        controlh = hgate.control(label='my ch')
+        circ.append(hgate, [0])
+        circ.append(controlh, [0, 1])
+        circ.append(controlh, [1, 0])
+
+        self.assertEqual(str(_text_circuit_drawer(circ)), expected)
+
+    def test_control_gate_label_with_cond_1_low(self):
+        """Control gate has a label and a conditional (compression=low)
+        See https://github.com/Qiskit/qiskit-terra/issues/4361 """
+        expected = '\n'.join(["         my ch  ",
+                              "q_0: |0>───■────",
+                              "           │    ",
+                              "        ┌──┴───┐",
+                              "q_1: |0>┤ my h ├",
+                              "        └──┬───┘",
+                              "        ┌──┴──┐ ",
+                              " c_0: 0 ╡ = 1 ╞═",
+                              "        └─────┘ "])
+
+        qr = QuantumRegister(2, 'q')
+        cr = ClassicalRegister(1, 'c')
+        circ = QuantumCircuit(qr, cr)
+        hgate = HGate(label='my h')
+        controlh = hgate.control(label='my ch').c_if(cr, 1)
+        circ.append(controlh, [0, 1])
+
+        self.assertEqual(str(_text_circuit_drawer(circ, vertical_compression='low')), expected)
+
+    def test_control_gate_label_with_cond_1_med(self):
+        """Control gate has a label and a conditional (compression=med)
+        See https://github.com/Qiskit/qiskit-terra/issues/4361 """
+        expected = '\n'.join(["         my ch  ",
+                              "q_0: |0>───■────",
+                              "        ┌──┴───┐",
+                              "q_1: |0>┤ my h ├",
+                              "        └──┬───┘",
+                              "        ┌──┴──┐ ",
+                              " c_0: 0 ╡ = 1 ╞═",
+                              "        └─────┘ "])
+
+        qr = QuantumRegister(2, 'q')
+        cr = ClassicalRegister(1, 'c')
+        circ = QuantumCircuit(qr, cr)
+        hgate = HGate(label='my h')
+        controlh = hgate.control(label='my ch').c_if(cr, 1)
+        circ.append(controlh, [0, 1])
+
+        self.assertEqual(str(_text_circuit_drawer(circ, vertical_compression='medium')), expected)
+
+    def test_control_gate_label_with_cond_1_high(self):
+        """Control gate has a label and a conditional (compression=high)
+        See https://github.com/Qiskit/qiskit-terra/issues/4361 """
+        expected = '\n'.join(["         my ch  ",
+                              "q_0: |0>───■────",
+                              "        ┌──┴───┐",
+                              "q_1: |0>┤ my h ├",
+                              "        ├──┴──┬┘",
+                              " c_0: 0 ╡ = 1 ╞═",
+                              "        └─────┘ "])
+
+        qr = QuantumRegister(2, 'q')
+        cr = ClassicalRegister(1, 'c')
+        circ = QuantumCircuit(qr, cr)
+        hgate = HGate(label='my h')
+        controlh = hgate.control(label='my ch').c_if(cr, 1)
+        circ.append(controlh, [0, 1])
+
+        self.assertEqual(str(_text_circuit_drawer(circ, vertical_compression='high')), expected)
+
+    def test_control_gate_label_with_cond_2_med(self):
+        """Control gate has a label and a conditional (on label, compression=med)
+        See https://github.com/Qiskit/qiskit-terra/issues/4361 """
+        expected = '\n'.join(["        ┌──────┐",
+                              "q_0: |0>┤ my h ├",
+                              "        └──┬───┘",
+                              "q_1: |0>───■────",
+                              "         my ch  ",
+                              "        ┌──┴──┐ ",
+                              " c_0: 0 ╡ = 1 ╞═",
+                              "        └─────┘ "])
+
+        qr = QuantumRegister(2, 'q')
+        cr = ClassicalRegister(1, 'c')
+        circ = QuantumCircuit(qr, cr)
+        hgate = HGate(label='my h')
+        controlh = hgate.control(label='my ch').c_if(cr, 1)
+        circ.append(controlh, [1, 0])
+
+        self.assertEqual(str(_text_circuit_drawer(circ, vertical_compression='medium')), expected)
+
+    def test_control_gate_label_with_cond_2_low(self):
+        """Control gate has a label and a conditional (on label, compression=low)
+        See https://github.com/Qiskit/qiskit-terra/issues/4361 """
+        expected = '\n'.join(["        ┌──────┐",
+                              "q_0: |0>┤ my h ├",
+                              "        └──┬───┘",
+                              "           │    ",
+                              "q_1: |0>───■────",
+                              "         my ch  ",
+                              "        ┌──┴──┐ ",
+                              " c_0: 0 ╡ = 1 ╞═",
+                              "        └─────┘ "])
+
+        qr = QuantumRegister(2, 'q')
+        cr = ClassicalRegister(1, 'c')
+        circ = QuantumCircuit(qr, cr)
+        hgate = HGate(label='my h')
+        controlh = hgate.control(label='my ch').c_if(cr, 1)
+        circ.append(controlh, [1, 0])
+
+        self.assertEqual(str(_text_circuit_drawer(circ, vertical_compression='low')), expected)
+
 
 class TestTextDrawerParams(QiskitTestCase):
     """Test drawing parameters."""