#!/usr/bin/env bash # This file is part of Qtah. # # Copyright 2015-2020 The Qtah Authors. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . if (( BASH_VERSINFO[0] < 4 || ( BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 1 ) )); then echo "qtah-listener-gen: Error, this script depends on features in bash 4.1 and newer." >&2 echo "qtah-listener-gen: You are currently running version $BASH_VERSION, please upgrade." >&2 echo "qtah-listener-gen: Aborting." >&2 exit 1 fi set -euo pipefail msg() { echo "qtah-listener-gen: $*" } help() { cat < : Generates C++ listener classes in listener.cpp and listener.hpp in the given directory. --gen-hs-dir : Generates bindings to be linked into a Hoppy generator in the Haskell project rooted at the given directory. At least one of --gen-cpp-dir or --gen-hs-dir is required. EOF } cppDir= hsDir= while [[ $# -ne 0 ]]; do case "$1" in --help) help; exit 0;; --gen-cpp-dir) shift; cppDir="${1:?--gen-cpp-dir requires an argument.}";; --gen-hs-dir) shift; hsDir="${1:?--gen-hs-dir requires an argument.}";; *) msg "Unrecognized argument: $1"; exit 1;; esac shift done if [[ -z $cppDir ]] && [[ -z $hsDir ]]; then msg "At least one of --gen-cpp-dir or --gen-hs-dir is required. See --help." exit 1 fi installGen() { local src="${1:?}" dest="${2:?}" if cmp -s "$src" "$dest"; then rm "$src" else mv "$src" "$dest" fi } forEachListener() { local fn="${1:?forEachListener requires the name of a function to call.}" # Keep the includes in the C++ section up-to-date with the types used here. $fn Bool "bool" $fn Direction "QAbstractAnimation::Direction" $fn DockWidgetArea "Qt::DockWidgetArea" $fn DockWidgetAreas "Qt::DockWidgetAreas" $fn Double "double" $fn Int "int" $fn IntBool "int|bool" $fn IntExitStatus "int|QProcess::ExitStatus" $fn IntInt "int|int" $fn IntQlonglong "int|qlonglong" $fn Orientation "Qt::Orientation" $fn OrientationIntInt "Qt::Orientation|int|int" $fn ProcessError "QProcess::ProcessError" $fn ProcessState "QProcess::ProcessState" $fn PtrQAbstractButton "QAbstractButton*" $fn PtrQAbstractButtonBool "QAbstractButton*|bool" $fn PtrQAbstractItemModel "QAbstractItemModel*" $fn PtrQAction "QAction*" $fn PtrQMdiSubWindow "QMdiSubWindow*" $fn PtrQObject "QObject*" $fn PtrQTreeWidgetItem "QTreeWidgetItem*" $fn PtrQTreeWidgetItemInt "QTreeWidgetItem*|int" $fn PtrQTreeWidgetItemPtrQTreeWidgetItem "QTreeWidgetItem*|QTreeWidgetItem*" $fn PtrQWidgetPtrQWidget "QWidget*|QWidget*" $fn QAbstractAnimation "QAbstractAnimation*" $fn QAbstractSliderAction "QAbstractSlider::SliderAction" $fn QClipboardMode "QClipboard::Mode" $fn QDate "QDate" $fn QDockWidgetFeatures "QDockWidget::DockWidgetFeatures" $fn QModelIndex "QModelIndex" $fn QModelIndexIntInt "QModelIndex|int|int" $fn QModelIndexIntIntQModelIndexInt "QModelIndex|int|int|QModelIndex|int" $fn QModelIndexQModelIndex "QModelIndex|QModelIndex" $fn QModelIndexQModelIndexQVectorInt "QModelIndex|QModelIndex|QVector" $fn QPoint "QPoint" $fn QSize "QSize" $fn QString "QString" $fn QSystemTrayIconActivationReason "QSystemTrayIcon::ActivationReason" $fn QWindowVisibility "QWindow::Visibility" 0x050000 '[5, 0]' $fn Qlonglong "qlonglong" $fn Qreal "qreal" $fn RefConstQIcon "const QIcon&" $fn RefConstQItemSelectionRefConstQItemSelection "const QItemSelection&|const QItemSelection&" $fn RefConstQListQModelIndex "const QList&" $fn RefConstQModelIndex "const QModelIndex&" $fn RefConstQVariant "const QVariant&" $fn ScreenOrientation "Qt::ScreenOrientation" 0x050000 '[5, 0]' $fn StateState "QAbstractAnimation::State|QAbstractAnimation::State" $fn ToolBarAreas "Qt::ToolBarAreas" $fn ToolButtonStyle "Qt::ToolButtonStyle" $fn WindowModality "Qt::WindowModality" $fn WindowState "Qt::WindowState" $fn WindowStatesWindowStates "Qt::WindowStates|Qt::WindowStates" $fn "" "" } #### Generate C++ listener classes. if [[ -n $cppDir ]]; then msg "Generating C++ listener classes." exec \ {fhpp}>"$cppDir/listener.hpp.new" \ {fcpp}>"$cppDir/listener.cpp.new" sayHpp() { echo "$*" >&$fhpp; } sayCpp() { echo "$*" >&$fcpp; } sayHpp '////////// GENERATED FILE, EDITS WILL BE LOST //////////' sayHpp sayHpp '#ifndef QTAH_LISTENERS_HPP' sayHpp '#define QTAH_LISTENERS_HPP' sayHpp sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#include ' sayHpp '#if QT_VERSION >= 0x050000' sayHpp '#include ' sayHpp '#endif' sayHpp '#include "b_callback.hpp"' sayCpp '////////// GENERATED FILE, EDITS WILL BE LOST //////////' sayCpp sayCpp '#include "listener.hpp"' sayCpp sayCpp '#include ' writeCpp() { local -r name="${1?}" params="${2?}" minVersion="${3-}" local -r className="Listener${name}" local -r callbackClassName="Callback${name}Void" local paramList="" local paramTypeList="" local paramNameList="" local n=1 if [[ -n $params ]]; then while read type; do [[ -n $paramList ]] && paramList+=', ' [[ -n $paramTypeList ]] && paramTypeList+=',' [[ -n $paramNameList ]] && paramNameList+=', ' paramList+="${type} arg${n}" paramTypeList+="${type}" paramNameList+="arg${n}" ((n++)) done < <(tr '|' '\n' <<<"$params") fi if [[ -n $minVersion ]]; then sayHpp sayHpp "#if QT_VERSION >= ${minVersion}" fi sayHpp sayHpp "class ${className} : public QObject {" sayHpp " Q_OBJECT" sayHpp sayHpp "public:" sayHpp " typedef ${callbackClassName} callback;" sayHpp sayHpp " ${className}(QObject* source, const std::string& signal, callback f);" sayHpp " ~${className}();" sayHpp " bool isValid() const;" sayHpp sayHpp "public slots:" sayHpp " void invoke(${paramList});" sayHpp sayHpp "private:" sayHpp " callback f_;" sayHpp " QMetaObject::Connection connection_;" sayHpp "};" if [[ -n $minVersion ]]; then sayHpp sayHpp "#endif" fi if [[ -n $minVersion ]]; then sayCpp sayCpp "#if QT_VERSION >= ${minVersion}" fi sayCpp sayCpp "${className}::${className}(QObject* source, const std::string& signal, ${className}::callback f) :" sayCpp " QObject(source), f_(f) {" sayCpp " connection_ = connect(source, signal.c_str(), this, SLOT(invoke(${paramTypeList})));" sayCpp "}" sayCpp sayCpp "${className}::~${className}() {" sayCpp " QObject::disconnect(connection_);" sayCpp "}" sayCpp sayCpp "bool ${className}::isValid() const {" sayCpp " return static_cast(connection_);" sayCpp "}" sayCpp sayCpp "void ${className}::invoke(${paramList}) {" sayCpp " f_(${paramNameList});" sayCpp "}" if [[ -n $minVersion ]]; then sayCpp sayCpp "#endif" fi } forEachListener writeCpp sayHpp sayHpp '#endif' exec {fhpp}>&- {fcpp}>&- unset fhpp fcpp sayHpp sayCpp writeCpp installGen "$cppDir/listener.hpp"{.new,} installGen "$cppDir/listener.cpp"{.new,} fi #### Generate Haskell binding definitions for the listeners. if [[ -n $hsDir ]]; then msg "Generating Haskell listener binding definitions." exec {fhs}>"$hsDir/src/Graphics/UI/Qtah/Generator/Interface/Internal/Listener.hs.new" say() { echo "$*" >&$fhs; } say '---------- GENERATED FILE, EDITS WILL BE LOST ----------' say say 'module Graphics.UI.Qtah.Generator.Interface.Internal.Listener where' say say 'import qualified Foreign.Hoppy.Generator.Spec as S' say 'import qualified Foreign.Hoppy.Generator.Std.String as String' say 'import qualified Foreign.Hoppy.Generator.Types as T' say 'import qualified Foreign.Hoppy.Generator.Version as V' say 'import qualified Graphics.UI.Qtah.Generator.Config as Config' say 'import qualified Graphics.UI.Qtah.Generator.Interface.Core.QObject as QObject' say 'import qualified Graphics.UI.Qtah.Generator.Interface.Internal.Callback as C' say 'import qualified Graphics.UI.Qtah.Generator.Module as M' say 'import qualified Graphics.UI.Qtah.Generator.Types as U' say say '{-# ANN module "HLint: ignore Use camelCase" #-}' writeHs() { local -r name="${1?}" local -r className="Listener${name}" local -r classVar="c_${className}" local -r callbackVar="cb_${name}Void" local -r listenerVar="listener${name}" say say "${classVar} =" say " S.makeClass (S.ident \"${className}\") Nothing [QObject.c_QObject]" say " [ S.mkCtor \"new\"" say " [T.ptrT \$ T.objT QObject.c_QObject, T.objT String.c_string, T.callbackT C.${callbackVar}]" say " , S.mkConstMethod \"isValid\" S.np T.boolT" say " ]" say say "${listenerVar} = U.ListenerInfo ${classVar} C.${callbackVar}" } forEachListener writeHs say say "aModule :: M.AModule" say "aModule =" say " M.AHoppyModule \$" say " S.addReqIncludes [S.includeLocal \"listener.hpp\"] \$" say " S.moduleModify' (S.makeModule \"listener\" \"b_listener.hpp\" \"b_listener.cpp\") \$ do" say " S.moduleAddHaskellName [\"Internal\", \"Listener\"]" say " S.moduleAddExports \$ V.collect" cont="[" writeHs() { local -r name="${1?}" local -r minVersion="${4-}" local -r className="Listener${name}" local -r classVar="c_${className}" if [[ -n $minVersion ]]; then say " ${cont} V.test (Config.qtVersion >= ${minVersion}) \$ S.Export ${classVar}" else say " ${cont} V.just \$ S.Export ${classVar}" fi if [[ $cont = '[' ]]; then cont=','; fi } forEachListener writeHs say " ]" exec {fhs}>&- unset fhs writeHs installGen "$hsDir/src/Graphics/UI/Qtah/Generator/Interface/Internal/Listener.hs"{.new,} # Also generate a GHC .hs-boot file for cycles in the module graph around listeners. msg "Generating Haskell listener .hs-boot file." exec {fhs}>"$hsDir/src/Graphics/UI/Qtah/Generator/Interface/Internal/Listener.hs-boot.new" say() { echo "$*" >&$fhs; } say '---------- GENERATED FILE, EDITS WILL BE LOST ----------' say say 'module Graphics.UI.Qtah.Generator.Interface.Internal.Listener where' say say 'import Foreign.Hoppy.Generator.Spec (Class)' say 'import Graphics.UI.Qtah.Generator.Types (ListenerInfo)' say writeHs() { local -r name="${1?}" local -r className="Listener${name}" local -r classVar="c_${className}" local -r listenerVar="listener${name}" say "${classVar} :: Class" say "${listenerVar} :: ListenerInfo" } forEachListener writeHs exec {fhs}>&- unset fhs writeHs installGen "$hsDir/src/Graphics/UI/Qtah/Generator/Interface/Internal/Listener.hs-boot"{.new,} fi