Меnu:


La aplicación que hoy portaremos a commonQt en un aplicativo de administración de notas, nos permite crear notas y luego buscar con facilidad cualquier termino dentro de todo el conjunto de notas creadas. Este aplicativo usa por debajo sqlite.

Porque commonQt y no Qt solamente?

Risc es el futuro. Es una frase de la película Hackers de 1995. Bueno ya hace varios años que tenemos ARM que es un retoño de Risc. Comenzamos en el año 2007 con la tableta nokia n800, hace uno tres de años nos mudamos al teléfono nokia n900 y hace un par de años que tenemos a disposición una maquina permanentemente encendida que es la orange pi. Uno de los aplicativos que aun no hemos podido migrar de x86 a Arm es el dbdesigner-fork. Es para modelar bases de datos esta hecho en kylix (delphi para linux), ya hace muchos años que no se usa el kylix. Sin embargo el dbdesigner sigue funcionando, por lo tanto lo seguimos usando. Han habido intentos de migrarlo al Lazarus pero sin éxito hasta hoy. Y como nuestro editor de texto favorito es el Emacs, nos pareció un buen momento para hacer un pequeño aplicativo en commonQt con la mira de en un futuro cercano codificar una alternativa al dbdesigner en commonQt. Por el tamaño del dbdesigner, esperamos que el commonQt nos acelere el desarrollo. Ya veremos como nos va.

Que es lo que haremos

Portaremos el app hecho en wxwidgets a commonQt. Usaremos en ArchLinux el designer de Qt para diseñar la interfaz de usuario.

Anteriormente ya hemos escrito acerca de como hacer el aplicativo en 2 artículos:

  1. Ejemplo faltante en wxwidgets.
  2. Ejemplo faltante en wxwidgets parte 2.

Mostrando el app en wxwidgets

Mostrando el código lisp




(require 'qt)
(in-package :common-lisp-user)

(defpackage "PEAPP-QT"
  (:use cl qt clsql-sqlite3)
  (:export "TIMELINE-MAIN"))

(in-package :peapp-qt)
(defvar *entity-note* nil)
(named-readtables:in-readtable :qt)

(defun find-child (object name)
  (let ((children (#_children object)))
    (or
     (loop for child in children
           when (equal name (#_objectName child))
           return child)
     (loop for child in children
           thereis (find-child child name)))))

(defclass timeline-mainform ()
  (
   (tableWidget :accessor tableWidget)
   (lineEdit :accessor lineEdit)
   (detailDialog :accessor detailDialog)
   )
  (:metaclass qt-class)
  (:qt-superclass "QWidget")
  (:slots ("timeline-mainform-show-dialog-detail()" timeline-mainform-show-dialog-detail)
          ("timeline-mainform-selected-item()" timeline-mainform-selected-item)
          ("timeline-mainform-on-add()" timeline-mainform-on-add)
          ("timeline-mainform-on-delete()" timeline-mainform-on-delete)
          ;("textChanged(QString)" timeline-mainform-line-edit-text-changed)
          ("timeline-mainform-line-edit-text-changed()" timeline-mainform-line-edit-text-changed))
  (:override ("closeEvent" timeline-mainform-close-event)))

(defmethod initialize-instance :after ((instance timeline-mainform) &key)
  (new instance)
  (#_setWindowTitle instance "timeline on commonQt")
      (with-objects ((file (#_new QFile "/home/olla/dev/cl/timeline/timeline_main_form_widget.ui"))
                     (loader (#_new QUiLoader)))
                    (if (#_open file 1)
                        (let ((win (#_load loader file instance))
                              (layout (#_new QVBoxLayout))
                              )
                          (#_close file)
                          (#_addWidget layout win)
                          (#_setLayout instance layout)
                          (setf (tableWidget instance) (find-child win "tableWidget"))
                          (setf (lineEdit instance) (find-child win "lineEdit"))
                          (connect (find-child win "pushButton") "clicked()" instance "timeline-mainform-on-add()");add
                          (connect (find-child win "pushButton_2") "clicked()" instance "close()");cancel
                          (connect (find-child win "pushButton_3") "clicked()" instance "timeline-mainform-on-delete()");del
                          ;(connect (find-child win "pushButton_4") "clicked()" instance "timeline-mainform-on-ok()");ok
                          (connect (find-child win "tableWidget") "cellActivated(int, int)" instance "timeline-mainform-selected-item()")
                          ;(connect (find-child win "lineEdit") "textChanged(QString)" instance "textChanged(QString)") ; just widget value
                          (connect (find-child win "lineEdit") "textChanged(QString)" instance "timeline-mainform-line-edit-text-changed()")
                          (timeline-mainform-set-ui-properties instance)
                          (timeline-mainform-ui-load-data instance)
                          )
                      (error (concatenate 'string "Couldn't open " "filename" " file!") ))))



;(defun load-data(instance) (was as recalc on aproxgnuplot.lisp)

(defmethod timeline-mainform-set-ui-properties ((instance timeline-mainform))
    (#_setColumnCount (tableWidget instance) 2)
    (#_setHorizontalHeaderLabels (tableWidget instance) '("name" ""))
    (#_setResizeMode (#_horizontalHeader (tableWidget instance)) 0 (#_QHeaderView::Stretch))
                                        ; do not show numbers on rows
  )

(defmethod timeline-mainform-ui-load-data ((instance timeline-mainform))
  (let ((item)
        (count)
        (row-index 0)
        (search-input (string-trim " " (#_text (lineEdit instance))))
        (rows))
    (setq rows (get-all-my-notes-from-db search-input))
    (#_clearContents (tableWidget instance))
    (setq count (length rows))
    (#_setRowCount (tableWidget instance) count)
    (with-slots (tableWidget) instance
      (dolist (item-row rows)
        (when item-row
          (setf item (#_new QTableWidgetItem (cadr item-row)))
          (#_setItem tableWidget row-index 0 item)
          (setf item (#_new QTableWidgetItem (write-to-string (car item-row))))
          (#_setItem tableWidget row-index 1 item)
          (incf row-index)
          )
      ))
    )
  )

(defmethod timeline-mainform-close-event ((instance timeline-mainform) close-event)
  (clsql:disconnect)
  (#_accept close-event))

(defmethod timeline-mainform-show-dialog-detail ((instance timeline-mainform))
  (#_show (detailDialog instance))
  )


(defmethod timeline-mainform-selected-item ((instance timeline-mainform))
  (let ((item (if (#_item (tableWidget instance) (#_currentRow (tableWidget instance)) 1) (#_item (tableWidget instance) (#_currentRow (tableWidget instance)) 1) nil)))
    (when item
      (if (not *entity-note*)
          (setf *entity-note* (make-instance 'entity-note :ui-id (#_text item)))
          (setf (ui-id *entity-note*) (#_text item))
          )
      (load-from-db *entity-note*)
      ; load entity-data on dialog
      (#_setDate (date-obj (detailDialog instance)) (#_QDate::fromString (date *entity-note*) "yyyy-MM-dd"))
      (#_setText (title-obj (detailDialog instance)) (title *entity-note*))
      (#_setText (content-obj (detailDialog instance)) (description *entity-note*))
      ; set read-only the date nil is false (not nil) is true
      (#_setEnabled (date-obj (detailDialog instance)) nil)
      (#_show (detailDialog instance))
      )
    )
  )

(defmethod timeline-mainform-on-add ((instance timeline-mainform))

  (if (not *entity-note*)
      (setf *entity-note* (make-instance 'entity-note :ui-id nil :title "" :description ""))
      (progn
        (setf (ui-id *entity-note*) nil)
        (setf (title *entity-note*) "")
        (setf (date *entity-note*) nil)
        (setf (description *entity-note*) "")
        )
      )
  (#_setDate (date-obj (detailDialog instance)) (#_QDate::currentDate))
  (#_setText (title-obj (detailDialog instance)) (title *entity-note*))
  (#_setText (content-obj (detailDialog instance)) (description *entity-note*))
  (#_setEnabled (date-obj (detailDialog instance)) (not nil))
  (#_show (detailDialog instance))
  )

(defmethod timeline-mainform-on-delete ((instance timeline-mainform))

  (let (
        (mylist (#_selectedIndexes (tableWidget instance) ))
        (mycount)
        (my-row-index)
        (myitem)
        (my-yes-no-var)
        (result-yes (enum-value (#_QMessageBox::Yes)))
        )
    (when mylist
      (setq mycount (length mylist))
      (when (= (- mycount 1) 0)
        (setq my-row-index (#_row (car mylist)))
                                        ; get item
        (setq myitem (#_item (tableWidget instance) my-row-index 1))
        (when myitem
          (setq my-yes-no-var (#_QMessageBox::question instance "delete record" "Sure, about deletion?" (#_QMessageBox::Yes) (#_QMessageBox::No)))
          (when (= (enum-value my-yes-no-var)  result-yes)
            (if (not *entity-note*)
                (setf *entity-note* (make-instance 'entity-note :ui-id (#_text myitem)))
                (setf (ui-id *entity-note*) (#_text myitem))
                )
            (delete-from-db *entity-note*)
            ))))))

;(defun text-changed (widget value)
  ;(setf (value widget) (parse-date value)))
;(defun timeline-mainform-line-edit-text-changed (widget value)
(defmethod timeline-mainform-line-edit-text-changed  ((instance timeline-mainform))
                                        ;(print (concatenate 'string "timeline-mainform-text-changed0=" value))
  (let (
        (input (string-trim " " (#_text (lineEdit instance))))
        )
    (when (or (= (length input) 0) (> (length input) 0))
      (timeline-mainform-ui-load-data instance)
      )
    )
  )

(defun get-all-my-notes-from-db (search-input)
  (let ((sql-query "SELECT id, date(date) || ', ' || title FROM time_lines"))
    (if (> (length search-input) 0)
        (setq sql-query
              (concatenate 'string
                           sql-query
                           " where title like '%"
                           search-input
                           "%' or "
                           " description like '%"
                           search-input
                           "%' or "
                           " id like '%"
                           search-input
                           "%' "
              )
        ))
    (setq sql-query (concatenate 'string
                                 sql-query
                                 " order by date desc"))
    (clsql:query sql-query)
    )
  )

(defun get-note-from-db (id-param)
  (clsql:query
   (concatenate 'string "SELECT date(date), title, description FROM time_lines where id=" id-param))
  )

(defclass note-dialog ()
  ((date-obj :accessor date-obj)
   (title-obj :accessor title-obj)
   (content-obj :accessor content-obj)
   (parentFrame :accessor parentFrame)
   )
  (:metaclass qt-class)
  (:qt-superclass "QDialog")
  (:slots
   ("updateEntityObject()" note-dialog-update-entity-object))
  )


(defmethod initialize-instance :after ((window note-dialog) &key)
  (new window)
  (#_setWindowTitle window "Mis detalles")
  (let ((layout (#_new QFormLayout))
        (date-edit (#_new QDateEdit))
        (input (#_new QLineEdit  ""))
        (text-edit (#_new QTextEdit))
        (buttons (#_new QDialogButtonBox
                        (enum-or
                         (#_QDialogButtonBox::Ok)
                         (#_QDialogButtonBox::Cancel))
                        (#_Qt::Horizontal)))
        )
    (setf (date-obj window)  date-edit)
    (setf (title-obj window)  input)
    (setf (content-obj window)  text-edit)
    (#_setLayout window layout)
    (#_addWidget layout date-edit)
    (#_addWidget layout input)
    (#_addWidget layout text-edit)
    (#_addWidget layout buttons)
    (connect buttons "accepted()"
             window "updateEntityObject()")
    (connect buttons "accepted()"
             window "accept()")
    (connect buttons "rejected()"
             window "reject()"))
  )

(defmethod note-dialog-update-entity-object ((instance note-dialog))
  (setf (title *entity-note*) (#_text (title-obj instance)))
  (setf (date *entity-note*) (#_toString (#_date (date-obj instance) ) "yyyy-MM-dd"))
  (setf (description *entity-note*) (#_toPlainText (content-obj instance)))
                                        ; save object to db
  (if (ui-id *entity-note*)
      (update-to-db *entity-note*)
      (insert-to-db *entity-note*)
      )
                                        ; update list ui with the changes (chk wxwidgets)
  (timeline-mainform-ui-load-data  (parentFrame instance))
  )

(defclass entity-note ()
  ((ui-id :accessor ui-id
          :initarg  :ui-id)
   (title :accessor title
          :initarg :title)
   (description :accessor description
                :initarg :description)
   (date :accessor date)
   ))

(defmethod load-from-db ((entity entity-note))
  (let (
        (rows (get-note-from-db (ui-id entity))))
    (dolist (item-row rows)
      (setf (title entity) (cadr item-row))
      (setf (date entity) (car item-row))
      (setf (description entity) (cadr (cdr item-row)))
      )
    ;(print (format t " load-from-db ~A" (description defmethod)))
    )
  )

(defmethod insert-to-db ((entity entity-note))
  (if (not (ui-id entity))
      (clsql:execute-command (concatenate 'string "insert into time_lines (date, title, description, created_at) values (date('" (date entity) "'), '" (title entity) "', '" (description entity) "', datetime('now', 'localtime') )"))
      )
  )

(defmethod update-to-db ((entity entity-note))
  (if (ui-id entity)
      (clsql:execute-command (concatenate 'string "UPDATE time_lines set title='" (title entity) "', description='" (description entity) "', updated_at=datetime('now', 'localtime') where id=" (ui-id entity)))
      )
  )

(defmethod delete-from-db ((entity entity-note))
  (if (ui-id entity)
      (clsql:execute-command (concatenate 'string "delete from time_lines where id=" (ui-id entity)))
      )
  )

(defun timeline-main()
  (clsql:connect '("/home/olla/.wxtimeline/wx_timelines.sqlite3") :database-type :sqlite3)
  (qt:ensure-smoke "qtuitools")
  (make-qapplication)
  ;(make-qapplication "-display" "localhost:10.0") ;(*qapplication* var created just once)
  (with-objects (
                 (mainform (make-instance 'timeline-mainform))
                 (dialog_detail (make-instance 'note-dialog))
                 )
    (setf (detailDialog mainform) dialog_detail)
    (setf (parentFrame dialog_detail) mainform)
    (#_show mainform)
    (#_exec *qapplication*)))

Mostrando el form del designer-qt4

<?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>806</width>
    <height>475</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <layout class="QFormLayout" name="formLayout">
     <item row="0" column="0">
      <widget class="QLabel" name="label">
       <property name="text">
        <string>&amp;Search</string>
       </property>
      </widget>
     </item>
     <item row="0" column="1">
      <widget class="QLineEdit" name="lineEdit"/>
     </item>
    </layout>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout"/>
   </item>
   <item>
    <widget class="QTableWidget" name="tableWidget">
     <property name="columnCount">
      <number>3</number>
     </property>
     <attribute name="horizontalHeaderDefaultSectionSize">
      <number>100</number>
     </attribute>
     <column>
      <property name="text">
       <string>uno</string>
      </property>
     </column>
     <column>
      <property name="text">
       <string>dos</string>
      </property>
     </column>
     <column>
      <property name="text">
       <string>tres</string>
      </property>
     </column>
    </widget>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout_2">
     <item>
      <widget class="QPushButton" name="pushButton">
       <property name="text">
        <string>&amp;Add</string>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QPushButton" name="pushButton_3">
       <property name="text">
        <string>&amp;Del</string>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QPushButton" name="pushButton_4">
       <property name="text">
        <string>&amp;Ok</string>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QPushButton" name="pushButton_2">
       <property name="text">
        <string>&amp;Cancel</string>
       </property>
      </widget>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>

Mostrando el script de creación de la base de datos

echo "CREATE TABLE IF NOT EXISTS time_lines (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, title varchar(255), description text, date date, created_at datetime, updated_at datetime);" | sqlite3 wx_timelines.sqlite3

Mostrando el app en commonQt

Imagen muy parecida a la que ya vimos mas arriba.

Detalle de un registro.

Conclusión

Existen pocos ejemplos de aplicativos en commonQt. Imaginamos que este sera uno de los pocos blogpost acerca del tema en el presente año.

Last change: 21.12.2018 06:41

blog comments powered by Disqus