{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<p>\n",
    "<font size='5' face='Georgia, Arial'>IIC-2233 Apunte Programación Avanzada</font><br>\n",
    "<font size='1'> Modificado el 2026-1 por Equipo Docente IIC2233. </font>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [*QTimer*](#QTimer)\n",
    "   1. [Ejemplo-multiples-QTimer](#Ejemplo-multiples-QTimer)\n",
    "   2. [`singleShot`](#singleShot)\n",
    "   3. [Ejemplo Aplicado](#Ejemplo-Aplicado)\n",
    "2. [*Main Window*](#Main-Window)\n",
    "   1. [Vista en macOs](#Vista-en-macOS)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## QTimer"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Otra herramienta que existe para generar comportamiento de concurrencia dentro de PyQt es la clase `QTimer`. Los objetos de esta clase, a diferencia de los `QThread`, no son un símil a `Timer` del módulo `threading`: `Timer` después de un tiempo específico ejecuta una subrutina una única vez, mientras que `QTimer` ejecuta una subrutina cada cierto tiempo determinado periódicamente, repitiendo la subrutina una y otra vez.\n",
    "\n",
    "Este tipo de comportamiento es simulable utilizando `QThread`, al definir código para un *thread* de la forma:\n",
    "\n",
    "```python\n",
    "def run(self):\n",
    "    while True:\n",
    "        # Lo que quiero que el QThread haga en cada iteración\n",
    "        time.sleep(self.tiempo)\n",
    "```\n",
    "\n",
    "... y debería obtener el mismo resultado descrito al usar `QTimer`, lo que es cierto. La desventaja de realizar esto en comparación con usar un `QTimer` es que estos últimos están construidos para efectuar este comportamiento, mientras que los `QThreads`, como los *threads* en general, están construidos para que acaben eventualmente. Luego, el implementar un *thread* usando el código anterior nos obliga a usar el método `terminate`, que se considera **mala práctica** al forzar un *thread* a terminar, en vez de que este termine por su cuenta.\n",
    "\n",
    "Por su lado, `QTimer` provee métodos para comenzar (`start`) y detener (`stop`) la ejecución periódica de buena manera. Tras inicializar un `QTimer`, se le asigna mediante `setInterval` el tiempo en milisegundos que durará el periodo entre ejecuciones, y mediante el atributo (y señal) `timeout` se puede conectar a la subrutina que se efectuará una y otra vez: `timer.timeout.connect(subrutina)`.\n",
    "\n",
    "\n",
    "En el siguiente ejemplo crearemos un reloj digital que actualizará el tiempo cada 1 segundo. Este código se encuentra en el archivo `4-bonus-qtimer-y-main-window/1_reloj_digital.py`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```python\n",
    "from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout\n",
    "from PyQt6.QtCore import QTimer\n",
    "from PyQt6.QtGui import QFont\n",
    "import sys\n",
    "import datetime\n",
    "\n",
    "\n",
    "class RelojDigital(QWidget):\n",
    "    def __init__(self) -> None:\n",
    "        super().__init__()\n",
    "\n",
    "        # Crear label encargado de mostrar la hora\n",
    "        self.label_timer = QLabel()\n",
    "        self.label_timer.setFont(QFont(\"Times\", 50))\n",
    "\n",
    "        # Crear layout vertical para nuestro label\n",
    "        layout = QVBoxLayout()\n",
    "        layout.addWidget(self.label_timer)\n",
    "        self.setLayout(layout)\n",
    "\n",
    "        # Crear nuestro QTimer encargado de actualizar el tiempo cada 1 segundo\n",
    "        timer = QTimer(self)\n",
    "        timer.timeout.connect(self.mostrar_hora)\n",
    "        timer.setInterval(1000)\n",
    "        timer.start()\n",
    "\n",
    "        # Definir título y tamaño ventana\n",
    "        self.setWindowTitle(\"Reloj Digital con QTimer\")\n",
    "        self.setGeometry(100, 100, 250, 100)\n",
    "\n",
    "        # Ejecutar el método para mostrar hora por primera vez\n",
    "        self.mostrar_hora()\n",
    "\n",
    "        # Mostrar ventana\n",
    "        self.show()\n",
    "\n",
    "    def mostrar_hora(self) -> None:\n",
    "        # Obtener hora actual\n",
    "        hora_actual = datetime.datetime.now().time()\n",
    "        # Actualizar texto del label\n",
    "        self.label_timer.setText(hora_actual.strftime(\"%H:%M:%S %p\"))\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "\n",
    "    def hook(type_, value, traceback):\n",
    "        print(type_)\n",
    "        print(traceback)\n",
    "\n",
    "    sys.__excepthook__ = hook\n",
    "    app = QApplication([])\n",
    "    reloj = RelojDigital()\n",
    "    sys.exit(app.exec())\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Ejemplo múltiples QTimer "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A continuación se muestra el último ejemplo de `QThread` visto en el contenido anterior, pero adaptado a `QTimer` en vez de `QThread`. Este código se encuentra en el archivo `4-bonus-qtimer-y-main-window/2_multiples_qtimers.py`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```python\n",
    "import sys\n",
    "from PyQt6.QtCore import pyqtSignal, QObject, QTimer\n",
    "from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QPushButton\n",
    "\n",
    "\n",
    "class MiTimer(QObject):\n",
    "    senal_actualizar = pyqtSignal(int, str)\n",
    "\n",
    "    def __init__(self, indice: int, tiempo: float) -> None:\n",
    "        super().__init__()\n",
    "        self.indice = indice\n",
    "        self.tiempo = tiempo\n",
    "        self.indice_actual = 0\n",
    "        self.timer = QTimer(self)\n",
    "\n",
    "        # Acá se asigna el tiempo de duración del periodo entre ejecuciones\n",
    "        self.timer.setInterval(int(tiempo * 1000))\n",
    "        # Acá se conecta la subrutina que se ejecutará\n",
    "        self.timer.timeout.connect(self.enviar_dato)\n",
    "\n",
    "    def enviar_dato(self) -> None:\n",
    "        if self.indice_actual <= 9:\n",
    "            self.senal_actualizar.emit(self.indice, str(self.indice_actual))\n",
    "            self.indice_actual += 1\n",
    "        else:\n",
    "            self.senal_actualizar.emit(self.indice, \"Status: Qtimer terminado\")\n",
    "            self.timer.stop()\n",
    "\n",
    "    def comenzar(self) -> None:\n",
    "        self.timer.start()\n",
    "\n",
    "    def sigue_andando(self) -> bool:\n",
    "        return self.timer.isActive()\n",
    "\n",
    "\n",
    "class MiVentana(QWidget):\n",
    "    def __init__(self) -> None:\n",
    "        super().__init__()\n",
    "        self.timers = []\n",
    "        self.init_gui()\n",
    "\n",
    "    def init_gui(self) -> None:\n",
    "        # Configuramos los widgets de la interfaz\n",
    "        # Definimos un montón de labels que corresponderán a un Qtimer cada uno\n",
    "        self.labels = {i: QLabel(\"Status: esperando Qtimer\", self) for i in range(1, 6)}\n",
    "        self.boton = QPushButton(\"Ejecutar Qtimers\", self)\n",
    "        self.boton.clicked.connect(self.ejecutar_timers)\n",
    "\n",
    "        for i in range(1, 6):\n",
    "            self.labels[i].setGeometry(10, (i - 1) * 30, 230, 30)\n",
    "\n",
    "        self.boton.setGeometry(10, 150, 230, 30)\n",
    "        # Configuramos las propiedades de la ventana.\n",
    "        self.setWindowTitle(\"Ejemplo Qtimers\")\n",
    "        self.setGeometry(50, 50, 250, 200)\n",
    "        self.show()\n",
    "\n",
    "    def ejecutar_timers(self) -> None:\n",
    "        \"\"\"\n",
    "        Este método crea cinco timers cada vez que se presiona el botón en la\n",
    "        interfaz. Los timers recibirán como argumento el índice del label\n",
    "        que les corresponde y el tiempo que toman entre cada iteración.\n",
    "        \"\"\"\n",
    "        for timer in self.timers:\n",
    "            if timer.sigue_andando():\n",
    "                return\n",
    "\n",
    "        self.timers = []\n",
    "        for i in range(1, 6):\n",
    "            timer = MiTimer(i, i / 10)\n",
    "            # Se conecta la señal emitida por el timer a un método\n",
    "            # de la ventana\n",
    "            timer.senal_actualizar.connect(self.actualizar_labels)\n",
    "            self.timers.append(timer)\n",
    "            timer.comenzar()\n",
    "\n",
    "    def actualizar_labels(self, indice: int, texto: str) -> None:\n",
    "        \"\"\"\n",
    "        Este método actualiza el label correspondiente según los datos\n",
    "        enviados desde un timer a través del índice y aplica el texto.\n",
    "        \"\"\"\n",
    "        self.labels[indice].setText(texto)\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "\n",
    "    def hook(type_, value, traceback):\n",
    "        print(type_)\n",
    "        print(traceback)\n",
    "\n",
    "    sys.__excepthook__ = hook\n",
    "    app = QApplication([])\n",
    "    ventana = MiVentana()\n",
    "    sys.exit(app.exec())\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `singleShot`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Por defecto, un `QTimer` se ejecutará periódicamente cada vez que se cumpla el tiempo indicado en `setInterval`. No obstante, puede ocurrir una situación donde queremos que el `QTimer` ejecute su función una **única vez** después de transcurrir el tiempo esperado. \n",
    "\n",
    "Un ejemplo podría ser donde queremos que la ventana se cierre después de 10 segundos. Para modelar este ejemplo con `QTimer`, podríamos hacer:\n",
    "- Un `QTimer` que se ejecuta cada 1 segundos y posee un contador. Cuando este contador llega a 10, se cierra la ventana.\n",
    "- Un `QTimer` que se ejecuta cada 10 segundos. Una vez ejecutada su función, el mismo hace `stop` para no volver a ejecutarse. Además, aprovecha de cerrar la ventana.\n",
    "\n",
    "\n",
    "No obstante, también existe otra forma más que es utilizar `singleShot`. Este método nos permite indicar a `pyqt` que el `QTimer` solo se ejecutará 1 vez y luego debe ser detenido. De este modo no necesitamos incluir un `stop()`.\n",
    "\n",
    "A continuación vamos a tomar el ejemplo del `RelojAnalogico` pero vamos a incluir 2 `QTimer` que son `singleShot`. El primero, va a esperar 3 segundos y hará `hide` de la ventana, mientras que el segundo esperará 5 segundos y hará `show` de la ventana. Como son `singleShot`, solo se van a ejecutar una única vez y luego no se volverán a ejecutar.\n",
    "\n",
    "Este código se encuentra en el archivo `4-bonus-qtimer-y-main-window/3_singleshot.py`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```python\n",
    "from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout\n",
    "from PyQt6.QtCore import QTimer\n",
    "from PyQt6.QtGui import QFont\n",
    "import sys\n",
    "import datetime\n",
    "\n",
    "\n",
    "class RelojDigital(QWidget):\n",
    "    def __init__(self) -> None:\n",
    "        super().__init__()\n",
    "\n",
    "        # Crear label encargado de mostrar la hora\n",
    "        self.label_timer = QLabel()\n",
    "        self.label_timer.setFont(QFont(\"Times\", 50))\n",
    "\n",
    "        # Crear layout vertical para nuestro label\n",
    "        layout = QVBoxLayout()\n",
    "        layout.addWidget(self.label_timer)\n",
    "        self.setLayout(layout)\n",
    "\n",
    "        # Crear nuestro QTimer encargado de actualizar el tiempo cada 1 segundo\n",
    "        self.timer = QTimer(self)\n",
    "        self.timer.timeout.connect(self.mostrar_hora)\n",
    "        self.timer.setInterval(1000)\n",
    "        self.timer.start()\n",
    "\n",
    "        # Definir título y tamaño ventana\n",
    "        self.setWindowTitle(\"Reloj Digital con QTimer\")\n",
    "        self.setGeometry(100, 100, 250, 100)\n",
    "\n",
    "        # Ejecutar el método para mostrar hora por primera vez\n",
    "        self.mostrar_hora()\n",
    "\n",
    "        # Mostrar ventana\n",
    "        self.show()\n",
    "\n",
    "        # Creamos un QTimer que despues de 3 segundos va a esconder la ventana\n",
    "        self.timer_singleshot_hide = QTimer(self)\n",
    "        # self.timer_singleshot_hide.setSingleShot(True)\n",
    "        self.timer_singleshot_hide.timeout.connect(self.hide)\n",
    "        self.timer_singleshot_hide.setInterval(3000)\n",
    "        self.timer_singleshot_hide.start()\n",
    "\n",
    "        # Creamos otro QTimer que despues de 5 segundos va a mostrar la ventana\n",
    "        self.timer_singleshot_show = QTimer(self)\n",
    "        # self.timer_singleshot_show.setSingleShot(True)\n",
    "        self.timer_singleshot_show.timeout.connect(self.show)\n",
    "        self.timer_singleshot_show.setInterval(5000)\n",
    "        self.timer_singleshot_show.start()\n",
    "\n",
    "    def mostrar_hora(self) -> None:\n",
    "        # Obtener hora actual\n",
    "        hora_actual = datetime.datetime.now().time()\n",
    "        # Actualizar texto del label\n",
    "        self.label_timer.setText(hora_actual.strftime(\"%H:%M:%S %p\"))\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "\n",
    "    def hook(type_, value, traceback):\n",
    "        print(type_)\n",
    "        print(traceback)\n",
    "\n",
    "    sys.__excepthook__ = hook\n",
    "    app = QApplication([])\n",
    "    reloj = RelojDigital()\n",
    "    sys.exit(app.exec())\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mini ejercicio: Ejecuta este código una vez para ver qué sucede. Luego comenta las líneas que hacen `setSingleShot` para que ambos `QTimer` no sean `singleShot` y vuelve a ejecutar el código.  ¿Por qué crees que pasa esto? \n",
    "\n",
    "\n",
    "**Respuesta**: por defecto todo `QTimer` no es `singleShot`, así que cada 3 segundos se hará `hide` de la ventana mientras que cada 5 segundos se hará `show` de la ventana. Esto continuará hasta que se cierre la aplicación."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Ejemplo Aplicado"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora, vamos a realizar el mismo ejemplo del contenido anterior pero aplicando `QTimer`.\n",
    "\n",
    "![](img/ejemplo_aplicado.gif)\n",
    "\n",
    "En este ejemplo, tendremos diferentes cuadrados que van de un extremo a otro a diferente velocidad. Además, se puede hacer click en un cuadrado para cambiar su color y detener su movimiento. (Mini desafío: intenta detener todos los cuadrados).\n",
    "\n",
    "Para esto, dentro del directorio `scripts` hay un directorio llamado `ejemplo_aplicado`. En este tenemos los siguientes archivos:\n",
    "\n",
    "* `main.py`: es el archivo principal a ejecutar.\n",
    "* `parametros_general.py`: son constantes que se ocuparán tanto en _back-end_ y _front-end_.\n",
    "\n",
    "**backend**\n",
    "* `backend/logica_qthread.py`: es el archivo principal del _back-end_ donde se aplica toda la lógica del movimiento de los cuadrados. En este archivo se ocupan `QThread` para modelar cada cuadrado. Se recomienda ignorar este archivo para este ejemplo. Se utilizó en el contenido número 1.\n",
    "* `backend/logica_thread.py`: es el archivo principal del _back-end_ donde se aplica toda la lógica del movimiento de los cuadrados. En este archivo se ocupan `threading.Thread` para modelar cada cuadrado. La única diferencia con `logica_qthread` es la herencia y que se debe explicitar que cada cuadrado es un _daemon_. Se recomienda ignorar este archivo para este ejemplo. Se utilizó en el contenido número 1.\n",
    "* `backend/bonus_logica_qtimer.py`: es el mismo archivo que `logica_qthread.py` pero se utilizan `QTimer` en vez de `QThread`. **Este archivo se utilizará en este ejemplo**.\n",
    "* `backend/parametros_backend.py`: son constantes que se ocuparán únicamente en _back-end_.\n",
    "\n",
    "**frontend**\n",
    "* `frontend/ventana.py`: es el archivo principal del _front-end_ donde se crea la ventana y todo elemento visual.\n",
    "* `frontend/parametros_backend.py`: son constantes que se ocuparán únicamente en _front-end_.\n",
    "\n",
    "Para la correcta ejecución de este archivo, debes abrir tu terminal, desplazarte con `cd` hasta el directorio `scripts/ejemplo_aplicado/` y luego escribir `python3 main.py`.\n",
    "\n",
    "**Importante** para utilizar `backend/logica_qtimer.py`, debes:\n",
    "1. Ir a `main.py`\n",
    "2. Comentar la línea 5: `from backend.logica_qthread import Juego`\n",
    "3. Descomentar la línea 13: `from backend.bonus_logica_qtimer import Juego`\n",
    "\n",
    "Para esto vamos a utilizar el _back-end_ que utiliza `QTimer`. Podrás notar que el _front-end_ es el mismo, puesto que una correcta modularización nos permite reutilizar el mismo _front-end_, modificar el _back-end_ y que todo funcione correctamente."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## *Main Window*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las ventanas creadas mediante `QWidget` que hemos usado hasta ahora, son ventanas simples donde pueden ser ubicados otros *widgets*. PyQt ofrece un tipo de ventana más completa denominada `MainWindow`. Esta ventana permite crear el esqueleto clásico de una aplicación como la mostrada en la figura a continuación, con barra de estado, barra de herramientas y barra de menú.\n",
    "\n",
    "![](img/pyqt-mainwindow-layout.png)\n",
    "\n",
    "La **barra de estado** permite mostrar información del estado de la aplicación en la medida que el usuario interactúa con ella. Para crearla usamos el método `statusBar()` perteneciente a la clase `QApplication`.\n",
    "\n",
    "La **barra de menú** es una de las partes típicas de una GUI. Esta barra corresponde a un grupo de comandos organizados y agrupados de manera lógica en menús.\n",
    "\n",
    "La **barra de herramientas** provee un acceso rápido a la mayoría de los comandos usados frecuentemente, la que puedes mover en la misma interfaz.\n",
    "\n",
    "Finalmente, el contenido central o ***central widget*** corresponde al cuerpo de la ventana. Este *widget* puede contener cualquiera de los *widgets* en `QtWidgets`, como también alguna de las ventanas creadas en los ejemplos anteriores. Para agregar cualquier *widget* o formulario al *widget* central se utiliza el método `setCentralWidget(widget)`. \n",
    "\n",
    "El siguiente ejemplo muestra cómo integrar los elementos descritos en la ventana principal. Este código se encuentra en el archivo `4-bonus-qtimer-y-main-window/4_mainwindows.py`. Dado el uso de _paths_ relativos, la terminal **debe** estar posicionada dentro del directorio `scripts/4-bonus-qtimer-y-main-window` para hacer `python3 4_mainwindows.py`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```python\n",
    "import os\n",
    "import sys\n",
    "\n",
    "from PyQt6.QtCore import pyqtSignal\n",
    "from PyQt6.QtGui import QIcon, QAction\n",
    "from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget\n",
    "from PyQt6.QtWidgets import QPushButton, QLabel, QLineEdit\n",
    "\n",
    "class MiVentana(QWidget):\n",
    "    def __init__(self, status_bar_signal: pyqtSignal) -> None:\n",
    "        super().__init__()\n",
    "        self.status_bar_signal = status_bar_signal\n",
    "        self.init_gui()\n",
    "\n",
    "    def init_gui(self) -> None:\n",
    "        \"\"\"\n",
    "        Este método inicializa el main widget y sus elementos.\n",
    "        \"\"\"\n",
    "        self.label_text = QLabel(\"Texto\", self)\n",
    "        self.print_label = QLabel(\"Print texto:\", self)\n",
    "        self.line_edit = QLineEdit(\"\", self)\n",
    "\n",
    "        self.boton = QPushButton(\"&Procesar\", self)\n",
    "        self.boton.resize(self.boton.sizeHint())\n",
    "        self.boton.clicked.connect(self.boton_callback)\n",
    "\n",
    "        self.label_text.move(20, 15)\n",
    "        self.line_edit.setGeometry(55, 15, 100, 20)\n",
    "        self.print_label.move(20, 50)\n",
    "        self.boton.move(20, 80)\n",
    "\n",
    "    def boton_callback(self) -> None:\n",
    "        \"\"\"\n",
    "        Este método es el encargado ejecutar una acción cada vez que el botón\n",
    "        es presionado. En esta caso, realiza el cambio en print_label y el status bar\n",
    "        mediate la emisión de una señal en la cual se envía el texto correspondiente.\n",
    "        \"\"\"\n",
    "        self.print_label.setText(f\"Print texto: {self.line_edit.text()}\")\n",
    "        self.print_label.resize(self.print_label.sizeHint())\n",
    "        self.status_bar_signal.emit(f\"QEdit: {self.line_edit.text()}\")\n",
    "\n",
    "\n",
    "class MainWindow(QMainWindow):\n",
    "    # Esta señal permite comunicar la barra de estados con el resto de los widgets\n",
    "    # en el formulario, incluidos el central widget.\n",
    "    onchange_status_bar = pyqtSignal(str)\n",
    "\n",
    "    def __init__(self) -> None:\n",
    "        super().__init__()\n",
    "\n",
    "        \"\"\"Configuramos la geometría de la ventana.\"\"\"\n",
    "        self.setWindowTitle(\"Ventana con Boton\")\n",
    "        self.setGeometry(200, 100, 300, 250)\n",
    "\n",
    "        \"\"\"Configuramos las acciones.\"\"\"\n",
    "        ver_status = QAction(QIcon(None), \"&Cambiar Status\", self)\n",
    "        ver_status.setStatusTip(\"Este es un ítem de prueba\")\n",
    "        ver_status.triggered.connect(self.cambiar_status_bar)\n",
    "\n",
    "        limpiar_status = QAction(QIcon(None), \"&Limpiar Status\", self)\n",
    "        limpiar_status.setStatusTip(\"Esta acción limpia la barra de estado\")\n",
    "        limpiar_status.triggered.connect(self.limpiar_status_bar)\n",
    "\n",
    "        buscar = QAction(QIcon(os.path.join(\"img\", \"search_icon.png\")), \"&Search\", self)\n",
    "        buscar.setStatusTip(\"Un ícono de búsqueda\")\n",
    "\n",
    "        salir = QAction(QIcon(None), \"&Exit\", self)\n",
    "        salir.setShortcut(\"Ctrl+Q\")\n",
    "        salir.setStatusTip(\"Salir de la aplicación\")\n",
    "        salir.triggered.connect(QApplication.quit)\n",
    "\n",
    "        \"\"\"Creamos la barra de menú.\"\"\"\n",
    "        menubar = self.menuBar()\n",
    "        archivo_menu = menubar.addMenu(\"Archivo\")  # primer menú\n",
    "        archivo_menu.addAction(ver_status)\n",
    "        archivo_menu.addAction(salir)\n",
    "\n",
    "        otro_menu = menubar.addMenu(\"Otro Menú\")  # segundo menú\n",
    "        otro_menu.addAction(limpiar_status)\n",
    "\n",
    "        \"\"\"Creamos la barra de herramientas.\"\"\"\n",
    "        toolbar = self.addToolBar(\"Toolbar\")\n",
    "        toolbar.addAction(buscar)\n",
    "        toolbar.addAction(salir)\n",
    "\n",
    "        \"\"\"Incluimos la barra de estado.\"\"\"\n",
    "        self.statusBar().showMessage(\"Listo\")\n",
    "        self.onchange_status_bar.connect(self.actualizar_status_bar)\n",
    "\n",
    "        \"\"\"\n",
    "        Configuramos el widget central con una instancia de la clase\n",
    "        MiVentana(). Además cargamos la señal en el central widget para\n",
    "        que este pueda interactuar con la barra de estados de la ventana\n",
    "        principal.\n",
    "        \"\"\"\n",
    "        self.form = MiVentana(self.onchange_status_bar)\n",
    "        self.setCentralWidget(self.form)\n",
    "\n",
    "    def cambiar_status_bar(self) -> None:\n",
    "        self.statusBar().showMessage(\"Cambié el Status\")\n",
    "\n",
    "    def limpiar_status_bar(self) -> None:\n",
    "        self.statusBar().showMessage(\"Status limpio.\")\n",
    "\n",
    "    def actualizar_status_bar(self, msg: str) -> None:\n",
    "        self.statusBar().showMessage(f\"Actualizado. {msg}\")\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "\n",
    "    def hook(type, value, traceback):\n",
    "        print(type)\n",
    "        print(traceback)\n",
    "\n",
    "    sys.__excepthook__ = hook\n",
    "\n",
    "    app = QApplication([])\n",
    "    ventana = MainWindow()\n",
    "    ventana.show()\n",
    "    sys.exit(app.exec())\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Vista en macOS"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuando trabajamos con `QMainWindows` en macOS, la barra del menú no está junto a nuestra ventana, sino que está en una barra externa. Esta barra está en la parte superior de nuestra pantalla junto a al logo de Apple. A continuación se muestra una imagen de la ventana creada en el ejemplo anterior y donde está la barra del menú.\n",
    "\n",
    "![](img/pyqt-mainwindow-mac.png)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.6"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  },
  "vscode": {
   "interpreter": {
    "hash": "a3a63619a7d9caf1f944c17a21d0e2ba5f9b4e8cf352ed74fca5bac06f551692"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
