Repository: Qiskit/qiskit

Issue:
Display of controlled composite gates appears incorrect.
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**:
- **Python version**:
- **Operating system**:

### What is the current behavior?
Controlled composite gates appear with the wrong number of controls.


### Steps to reproduce the problem
```
qc1 = QuantumCircuit(3, name='ghz')
qc1.h(0)
qc1.cx(0, 1)
qc1.cx(1, 2)
ghz = qc1.to_gate()
cghz = ghz.control(1)
qc2 = QuantumCircuit(4)
qc2.append(cghz, [1, 0, 2, 3])
print(qc2)
```
results in,
```
               
q_0: |0>───■───
           │   
q_1: |0>───■───
           │   
q_2: |0>───■───
        ┌──┴──┐
q_3: |0>┤ Ghz ├
        └─────┘
```
### What is the expected behavior?
The number of controls should be `num_ctrl_qubits`. The exception might be if the gate being controlled is itself a controlled gate. For instance a gate like x.control().control() would show two control qubits. 


### Suggested solutions





Discussion:
The expected result is this (see https://github.com/Qiskit/qiskit-terra/issues/3199)?
```
         ┌──────┐
q_0: |0>─┤1     ├ 
         │      │ 
q_1: |0>─■      ├
         │  Ghz │
q_2: |0>─┤2     ├
         │      │
q_3: |0>─┤3     ├
         └──────┘
```
That looks better.

Resolving patch:
diff --git a/qiskit/visualization/text.py b/qiskit/visualization/text.py
--- a/qiskit/visualization/text.py
+++ b/qiskit/visualization/text.py
@@ -236,7 +236,7 @@ def __init__(self, label="", top_connect=None, wire_label=''):
         self.bot_connect = self.bot_pad = " "
         self.mid_content = ""  # The label will be put by some other part of the box.
         self.left_fill = len(self.wire_label)
-        self.top_format = "┌{}─%s─┐".format(self.top_pad * self.left_fill)
+        self.top_format = "┌{}%s──┐".format(self.top_pad * self.left_fill)
         self.mid_format = "┤{} %s ├".format(self.wire_label)
         self.bot_format = "│{} %s │".format(self.bot_pad * self.left_fill)
         self.top_connect = top_connect if top_connect else '─'
@@ -259,15 +259,18 @@ def __init__(self, label, input_length, order, wire_label=''):
 class BoxOnQuWireMid(BoxOnWireMid, BoxOnQuWire):
     """ Draws the middle part of a box that affects more than one quantum wire"""
 
-    def __init__(self, label, input_length, order, wire_label=''):
+    def __init__(self, label, input_length, order, wire_label='', control_label=False):
         super().__init__(label, input_length, order, wire_label=wire_label)
-        self.mid_format = "┤{} %s ├".format(self.wire_label)
+        if control_label:
+            self.mid_format = "■{} %s ├".format(self.wire_label)
+        else:
+            self.mid_format = "┤{} %s ├".format(self.wire_label)
 
 
 class BoxOnQuWireBot(MultiBox, BoxOnQuWire):
     """ Draws the bottom part of a box that affects more than one quantum wire"""
 
-    def __init__(self, label, input_length, bot_connect='─', wire_label='', conditional=False):
+    def __init__(self, label, input_length, bot_connect=None, wire_label='', conditional=False):
         super().__init__(label)
         self.wire_label = wire_label
         self.top_pad = " "
@@ -275,6 +278,7 @@ def __init__(self, label, input_length, bot_connect='─', wire_label='', condit
         self.top_format = "│{} %s │".format(self.top_pad * self.left_fill)
         self.mid_format = "┤{} %s ├".format(self.wire_label)
         self.bot_format = "└{}%s──┘".format(self.bot_pad * self.left_fill)
+        bot_connect = bot_connect if bot_connect else '─'
         self.bot_connect = '┬' if conditional else bot_connect
 
         self.mid_content = self.top_connect = ""
@@ -297,7 +301,7 @@ def __init__(self, label="", top_connect=None, wire_label=''):
 class BoxOnClWireMid(BoxOnWireMid, BoxOnClWire):
     """ Draws the middle part of a conditional box that affects more than one classical wire"""
 
-    def __init__(self, label, input_length, order, wire_label=''):
+    def __init__(self, label, input_length, order, wire_label='', **_):
         super().__init__(label, input_length, order, wire_label=wire_label)
         self.mid_format = "╡{} %s ╞".format(self.wire_label)
 
@@ -314,6 +318,7 @@ def __init__(self, label, input_length, bot_connect='─', wire_label='', **_):
         self.top_format = "│{} %s │".format(self.top_pad * self.left_fill)
         self.mid_format = "╡{} %s ╞".format(self.wire_label)
         self.bot_format = "└{}%s──┘".format(self.bot_pad * self.left_fill)
+        bot_connect = bot_connect if bot_connect else '─'
         self.bot_connect = bot_connect
 
         self.mid_content = self.top_connect = ""
@@ -770,6 +775,41 @@ def normalize_width(layer):
         for instruction in instructions:
             instruction.layer_width = longest
 
+    @staticmethod
+    def controlled_wires(instruction, layer):
+        """
+        Analyzes the instruction in the layer and checks if the controlled arguments are in
+        the box or out of the box.
+
+        Args:
+            instruction (Instruction): instruction to analyse
+            layer (Layer): The layer in which the instruction is inserted.
+
+        Returns:
+            Tuple(list, list, list):
+              - controlled arguments on top of the "instruction box"
+              - controlled arguments on bottom of the "instruction box"
+              - controlled arguments in the "instruction box"
+              - the rest of the arguments
+        """
+        ctrl_qubits = instruction.qargs[:instruction.op.num_ctrl_qubits]
+        args_qubits = instruction.qargs[instruction.op.num_ctrl_qubits:]
+
+        in_box = list()
+        top_box = list()
+        bot_box = list()
+
+        qubit_index = sorted([i for i, x in enumerate(layer.qregs) if x in args_qubits])
+
+        for ctrl_qubit in ctrl_qubits:
+            if min(qubit_index) > layer.qregs.index(ctrl_qubit):
+                top_box.append(ctrl_qubit)
+            elif max(qubit_index) < layer.qregs.index(ctrl_qubit):
+                bot_box.append(ctrl_qubit)
+            else:
+                in_box.append(ctrl_qubit)
+        return (top_box, bot_box, in_box, args_qubits)
+
     def _instruction_to_gate(self, instruction, layer):
         """ Convert an instruction into its corresponding Gate object, and establish
         any connections it introduces between qubits"""
@@ -787,9 +827,10 @@ def _instruction_to_gate(self, instruction, layer):
         # add in a gate that operates over multiple qubits
         def add_connected_gate(instruction, gates, layer, current_cons):
             for i, gate in enumerate(gates):
-                layer.set_qubit(instruction.qargs[i], gate)
                 actual_index = self.qregs.index(instruction.qargs[i])
-                current_cons.append((actual_index, gate))
+                if actual_index not in [i for i, j in 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 \
@@ -881,9 +922,23 @@ def add_connected_gate(instruction, gates, layer, current_cons):
         elif isinstance(instruction.op, ControlledGate):
             label = TextDrawing.label_for_box(instruction, controlled=True)
             gates = []
-            for _ in instruction.qargs[:-1]:
+
+            params_array = TextDrawing.controlled_wires(instruction, layer)
+            controlled_top, controlled_bot, controlled_edge, rest = params_array
+            for _ in controlled_top + controlled_bot + controlled_edge:
                 gates.append(Bullet(conditional=conditional))
-            gates.append(BoxOnQuWire(label, conditional=conditional))
+            if len(rest) > 1:
+                top_connect = '┴' if controlled_top else None
+                bot_connect = '┬' if controlled_bot else None
+                indexes = layer.set_qu_multibox(rest, label,
+                                                conditional=conditional,
+                                                controlled_edge=controlled_edge,
+                                                top_connect=top_connect, bot_connect=bot_connect)
+                for index in range(min(indexes), max(indexes) + 1):
+                    # Dummy element to connect the multibox with the bullets
+                    current_cons.append((index, DrawElement('')))
+            else:
+                gates.append(BoxOnQuWire(label, conditional=conditional))
             add_connected_gate(instruction, gates, layer, current_cons)
 
         elif len(instruction.qargs) >= 2 and not instruction.cargs:
@@ -972,7 +1027,8 @@ def set_clbit(self, clbit, element):
         """
         self.clbit_layer[self.cregs.index(clbit)] = element
 
-    def _set_multibox(self, label, qubits=None, clbits=None, top_connect=None, conditional=False):
+    def _set_multibox(self, label, qubits=None, clbits=None, top_connect=None,
+                      bot_connect=None, conditional=False, controlled_edge=None):
         if qubits is not None and clbits is not None:
             qubits = list(qubits)
             clbits = list(clbits)
@@ -1005,8 +1061,7 @@ def _set_multibox(self, label, qubits=None, clbits=None, top_connect=None, condi
                                                          wire_label=wire_label))
             self.set_clbit(clbits.pop(0),
                            BoxOnClWireBot(label, box_height, wire_label=cargs.pop(0)))
-            return
-
+            return cbit_index
         if qubits is None and clbits is not None:
             bits = list(clbits)
             bit_index = sorted([i for i, x in enumerate(self.cregs) if x in bits])
@@ -1030,12 +1085,16 @@ def _set_multibox(self, label, qubits=None, clbits=None, top_connect=None, condi
         else:
             raise VisualizationError("_set_multibox error!.")
 
+        if controlled_edge:
+            control_index = [i for i, x in enumerate(self.qregs) if x in controlled_edge]
+        else:
+            control_index = []
+
         if len(bit_index) == 1:
             set_bit(bits[0], OnWire(label, top_connect=top_connect))
         else:
             box_height = max(bit_index) - min(bit_index) + 1
-            set_bit(bits.pop(0),
-                    OnWireTop(label, top_connect=top_connect, wire_label=qargs.pop(0)))
+            set_bit(bits.pop(0), OnWireTop(label, top_connect=top_connect, wire_label=qargs.pop(0)))
             for order, bit_i in enumerate(range(min(bit_index) + 1, max(bit_index))):
                 if bit_i in bit_index:
                     named_bit = bits.pop(0)
@@ -1043,9 +1102,11 @@ def _set_multibox(self, label, qubits=None, clbits=None, top_connect=None, condi
                 else:
                     named_bit = (self.qregs + self.cregs)[bit_i]
                     wire_label = ' ' * len(qargs[0])
-                set_bit(named_bit, OnWireMid(label, box_height, order, wire_label=wire_label))
-            set_bit(bits.pop(0), OnWireBot(label, box_height, wire_label=qargs.pop(0),
-                                           conditional=conditional))
+                set_bit(named_bit, OnWireMid(label, box_height, order, wire_label=wire_label,
+                                             control_label=bit_i in control_index))
+            set_bit(bits.pop(0), OnWireBot(label, box_height, bot_connect=bot_connect,
+                                           wire_label=qargs.pop(0), conditional=conditional))
+        return bit_index
 
     def set_cl_multibox(self, creg, label, top_connect='┴'):
         """Sets the multi clbit box.
@@ -1058,15 +1119,23 @@ def set_cl_multibox(self, creg, label, top_connect='┴'):
         clbit = [bit for bit in self.cregs if bit.register == creg]
         self._set_multibox(label, clbits=clbit, top_connect=top_connect)
 
-    def set_qu_multibox(self, bits, label, conditional=False):
+    def set_qu_multibox(self, bits, label, top_connect=None, bot_connect=None,
+                        conditional=False, controlled_edge=None):
         """Sets the multi qubit box.
 
         Args:
             bits (list[int]): A list of affected bits.
             label (string): The label for the multi qubit box.
+            top_connect (char): None or a char connector on the top
+            bot_connect (char): None or a char connector on the bottom
             conditional (bool): If the box has a conditional
+            controlled_edge (list): A list of bit that are controlled (to draw them at the edge)
+        Return:
+            List: A list of indexes of the box.
         """
-        self._set_multibox(label, qubits=bits, conditional=conditional)
+        return self._set_multibox(label, qubits=bits, top_connect=top_connect,
+                                  bot_connect=bot_connect,
+                                  conditional=conditional, controlled_edge=controlled_edge)
 
     def connect_with(self, wire_char):
         """Connects the elements in the layer using wire_char.


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
@@ -1737,6 +1737,123 @@ def test_c3u2(self):
         circuit.append(U2Gate(pi, -5 * pi / 8).control(3), [qr[0], qr[3], qr[2], qr[1]])
         self.assertEqual(str(_text_circuit_drawer(circuit)), expected)
 
+    def test_controlled_composite_gate_edge(self):
+        """Controlled composite gates (edge)
+        See: https://github.com/Qiskit/qiskit-terra/issues/3546 """
+        expected = '\n'.join(["        ┌──────┐",
+                              "q_0: |0>┤0     ├",
+                              "        │      │",
+                              "q_1: |0>■      ├",
+                              "        │  Ghz │",
+                              "q_2: |0>┤1     ├",
+                              "        │      │",
+                              "q_3: |0>┤2     ├",
+                              "        └──────┘"])
+        ghz_circuit = QuantumCircuit(3, name='ghz')
+        ghz_circuit.h(0)
+        ghz_circuit.cx(0, 1)
+        ghz_circuit.cx(1, 2)
+        ghz = ghz_circuit.to_gate()
+        cghz = ghz.control(1)
+        circuit = QuantumCircuit(4)
+        circuit.append(cghz, [1, 0, 2, 3])
+
+        self.assertEqual(str(_text_circuit_drawer(circuit)), expected)
+
+    def test_controlled_composite_gate_top(self):
+        """Controlled composite gates (top) """
+        expected = '\n'.join(["                ",
+                              "q_0: |0>───■────",
+                              "        ┌──┴───┐",
+                              "q_1: |0>┤0     ├",
+                              "        │      │",
+                              "q_2: |0>┤2 Ghz ├",
+                              "        │      │",
+                              "q_3: |0>┤1     ├",
+                              "        └──────┘"])
+        ghz_circuit = QuantumCircuit(3, name='ghz')
+        ghz_circuit.h(0)
+        ghz_circuit.cx(0, 1)
+        ghz_circuit.cx(1, 2)
+        ghz = ghz_circuit.to_gate()
+        cghz = ghz.control(1)
+        circuit = QuantumCircuit(4)
+        circuit.append(cghz, [0, 1, 3, 2])
+
+        self.assertEqual(str(_text_circuit_drawer(circuit)), expected)
+
+    def test_controlled_composite_gate_bot(self):
+        """Controlled composite gates (bottom) """
+        expected = '\n'.join(["        ┌──────┐",
+                              "q_0: |0>┤1     ├",
+                              "        │      │",
+                              "q_1: |0>┤0 Ghz ├",
+                              "        │      │",
+                              "q_2: |0>┤2     ├",
+                              "        └──┬───┘",
+                              "q_3: |0>───■────",
+                              "                "])
+        ghz_circuit = QuantumCircuit(3, name='ghz')
+        ghz_circuit.h(0)
+        ghz_circuit.cx(0, 1)
+        ghz_circuit.cx(1, 2)
+        ghz = ghz_circuit.to_gate()
+        cghz = ghz.control(1)
+        circuit = QuantumCircuit(4)
+        circuit.append(cghz, [3, 1, 0, 2])
+
+        self.assertEqual(str(_text_circuit_drawer(circuit)), expected)
+
+    def test_controlled_composite_gate_top_bot(self):
+        """Controlled composite gates (top and bottom) """
+        expected = '\n'.join(["                ",
+                              "q_0: |0>───■────",
+                              "        ┌──┴───┐",
+                              "q_1: |0>┤0     ├",
+                              "        │      │",
+                              "q_2: |0>┤1 Ghz ├",
+                              "        │      │",
+                              "q_3: |0>┤2     ├",
+                              "        └──┬───┘",
+                              "q_4: |0>───■────",
+                              "                "])
+        ghz_circuit = QuantumCircuit(3, name='ghz')
+        ghz_circuit.h(0)
+        ghz_circuit.cx(0, 1)
+        ghz_circuit.cx(1, 2)
+        ghz = ghz_circuit.to_gate()
+        ccghz = ghz.control(2)
+        circuit = QuantumCircuit(5)
+        circuit.append(ccghz, [4, 0, 1, 2, 3])
+
+        self.assertEqual(str(_text_circuit_drawer(circuit)), expected)
+
+    def test_controlled_composite_gate_all(self):
+        """Controlled composite gates (top, bot, and edge) """
+        expected = '\n'.join(["                ",
+                              "q_0: |0>───■────",
+                              "        ┌──┴───┐",
+                              "q_1: |0>┤0     ├",
+                              "        │      │",
+                              "q_2: |0>■      ├",
+                              "        │  Ghz │",
+                              "q_3: |0>┤1     ├",
+                              "        │      │",
+                              "q_4: |0>┤2     ├",
+                              "        └──┬───┘",
+                              "q_5: |0>───■────",
+                              "                "])
+        ghz_circuit = QuantumCircuit(3, name='ghz')
+        ghz_circuit.h(0)
+        ghz_circuit.cx(0, 1)
+        ghz_circuit.cx(1, 2)
+        ghz = ghz_circuit.to_gate()
+        ccghz = ghz.control(3)
+        circuit = QuantumCircuit(6)
+        circuit.append(ccghz, [0, 2, 5, 1, 3, 4])
+
+        self.assertEqual(str(_text_circuit_drawer(circuit)), expected)
+
 
 class TestTextWithLayout(QiskitTestCase):
     """The with_layout option"""