Traducciones en Qt (y algo más)

El sistema de traducciones de Qt es muy simple: usar el método QObject::tr() para definir los textos a traducir, crear un fichero .ts para cada idioma, ejecutar el comando lupdate para actualizar esos ficheros con las nuevas frases del proyecto, traducirlas y ejecutar lrelease para generar el fichero que usa Qt con las traducciones. El sistema es muy potente, por ejemplo, no hace falta nada especial para cambiar a idiomas orientales, simplemente funciona, además de proveer ayudas a la traducción como libros de frases para tener traducciones “pre-hechas”, o detectar incoherencias en parámetros o signos de puntuación distintos entre traducciones los muestra.

Como siempre, no quiero dar un curso, sólo recopilar algunos tips nacidos del día a día.

Frases dinámicas

Igual es de los consejos más importantes y “no opcionales”. Veamos.

“El carro de Pepe”, “Pepe’s car”. Un ejemplo rápido de que cada idioma tiene construcciones diferentes. ¿Y qué? Imaginemos que queremos que nuestra aplicación pregunte el nombre al usuario y muestre el mensaje anterior. Nuestro primer código podría ser algo como:

QString msg = tr("El carro de ") + strNombre;

El problema viene al traducirlo: la cadena a traducir es únicamente “El carro de “, mientras que en nuestro código concatenaremos el nombre a continuación. No es posible traducir la frase al inglés usando este método.

Qt ofrece una solución basada en parámetros: construimos una única frase indicando el lugar de los elementos. En tiempo de ejecución sustituimos esos parámetros con el método QString::arg. Así:

QString msg = tr("El carro de %1").arg(strNombre);

La cadena a traducir en inglés tendrá la forma “%1’s car”, y funcionará perfectamente.

Cambios en los textos originales

Un proyecto es un ser vivo, y es normal que los textos mostrados cambien. Cada vez que eso ocurre hay que ejecutar lupdate para actualizar el fichero .ts. Ahora bien, al cambiar textos o quitar frases, el fichero .ts guarda las viejas entradas (muy bueno por una parte para re-traducir, especialmente si son cambios menores como un signo de puntuación o una falta de ortografía en el texto original). Conforme crece el proyecto, las viejas entradas pueden molestar más que ayudar. Para eliminarlas basta con ejecutar lupdate -noobsolete (si se usa el plugin de Visual Studio se puede especificar este parámetro en las opciones del proyecto de Qt).

Propiedades personalizadas

Es posible definir en el Qt Designer propiedades personalizadas a los QWidgets (por ejemplo, para distinguir grupos de objetos de forma sencilla). No entraré en detalle ahora de esto, prometo un post otro día. ¿Qué tiene que ver con las traducciones? Que si el valor asignado es una cadena de texto, por defecto Qt Designer lo marca como “traducible”, por lo que nuestro fichero .ts podría verse desbordado de propiedades que no queremos traducir. Para evitarlo basta con desmarcar la casilla “Traducible” de la propiedad.

One more thing…

El útlimo consejo no es directamente propio de las traducciones sino de la clase QString y del Visual C++. El editor de código usa una codificación diferente a la que usa Qt por defecto, lo que puede llevar a que algunos caracteres se muestren mal al ejecutar la aplicación. Un caso típico es el símbolo de grado (º). El problema se magnifica si ese símbolo está en una traducción, ya que entonces Qt no asociará los textos ‘origen’ y ‘traducción’ correctamente. La solución más cómoda que he encontrado es usar un parámetro para ello e insertar el carácter Unicode correspondiente:

QString degreeMsg = tr("Ahora mismo hacen 25%1").arg(QChar(0260);

Más bocaditos de Qt

Sigo de lleno con Qt, eso es parte del motivo por el que lleve algo de tiempo sin escribir. Irónicamente, no tengo mucho para contar en esta ocasión, pero aprovecho para escribir algunos “bocaditos”.

Tipografías incrustadas

Dar un toque de distinción a nuestras aplicaciones puede incluir el utilizar tipografías que no son estándares, o que sólo vienen incluidas con instalaciones de determinados programas que no podemos asumir estén siempre presentes (ejemplo típico, MS Office).

La opción más conveniente es la de incluirlas con nuestra aplicación (no hablaré de licencias ni nada parecido, asumo que todo está en orden). Para esto se pueden bien copiar junto al ejecutable, o incluir en un fichero de recursos (QRC). En cualquier de ambos casos habrá que registrar las fuentes antes de poder utilizarlas, especialmente si deseamos usarlas desde hojas de estilos.

QFontDatabase::addApplicationFont(":/fonts/arialn.ttf");
QFontDatabase::addApplicationFont(":/fonts/arialnb.ttf");
QFontDatabase::addApplicationFont(":/fonts/arialnbi.ttf");
QFontDatabase::addApplicationFont(":/fonts/arialni.ttf");

Esto podemos hacerlo en nuestra función main(), justo después de instanciar la aplicación por ejemplo. A partir de ese momento podremos utilizar las fuentes por su nombre como si se tratase de otra fuente del sistema.

Qt5 y la “plaftorms/qwindows.dll”

Desde Qt5 existe una DLL nueva de la que no está muy bien documentada su importancia en el proceso de distribución de la aplicación. Se trata de la platforms/qwindows.dll. Junto a las DLL base, como Qt5Core.dll, Qt5Widgets.dll, etc. El caso es que si Qt no está instalado en el ordenador destino, debemos especificar la ruta hasta esta DLL. La forma más sencilla que he encontrado hasta ahora es especificar la ruta de la carpeta de plugins (donde también deberán estar las DLL para cargas imágenes, por cierto).

En Qt4 podíamos especificar la carpeta de plugins de la siguiente forma:

int main(int argc, char *argv[])
{
 QApplication a(argc, argv);

 QApplication::addLibraryPath(qApp->applicationDirPath() + "/qtplugins/");

 // ...
}

De esta forma tendríamos una carpeta “qtplugins” junto a nuestro ejecutable, y siempre podríamos obtener la ruta hasta ella. La diferencia ahora está en que en Qt5 debe hacerse antes de instanciar la aplicación, ya que es ahí cuando se cargará dicha DLL. El problema es que “qApp” es una macro de conveniencia a “QCoreApplication::instance()” (patrón singleton), por lo que es necesario que hayamos instanciado la clase antes de utilizarla. Esto nos lleva a que tenemos que asumir que el directorio actual sigue siendo el del ejecutable:

int main(int argc, char *argv[])
{
 QApplication::addLibraryPath("./qtplugins/");

 QApplication a(argc, argv);

 // ...
}

Esto sirve siempre y cuando no lancemos la aplicación desde un acceso directo que tenga cambiado el directorio de trabajo.

Extendiendo clases

Esto no es exclusivo de Qt, pero el ejemplo lo tomo de ahí. En Qt4, el método “tabBar()” de la clase QTabWidget era protegido (a partir de Qt5 es público). Si queríamos acceder a él teníamos que recurrir a la herencia para crear un método público tal que

class MyTabWidget : public QTabWidget {
public:
 QTabBar *tabBar()
 {
 return QTabWidget::tabBar();
 }
};

Ahora bien, una cosa que no todos los programadores de C++ sabrán, pero que verán claro como el agua después de leerlo: no es necesario crear el nuestro TabWidget utilizando el objeto MyTabWidget, podemos hacerlo con QTabWidget (por ejemplo, desde Qt Designer), y luego hacer un simple cast para acceder al método público.

((MyTabWidget*)ui.tabWidget)->tabBar()->setTabText(0, tr("Customers"));

Este “truco” podemos hacerlo con cualquier clase y con cualquier número de métodos, accediendo a atributos y métodos protegidos (nunca privados), siempre y cuando no declaremos nuevos atributos. Esto se debe a que la definición de nuevos métodos no modifica el contenido del objeto, por lo que el casting sigue siendo válido (el objeto sigue teniendo la estructura del tipo base), pero con una tabla de métodos extendida.

Objective-C tiene un mecanismo similar, aunque algo más elegante (y con una implementación diferente debido a que usa paso de mensajes en lugar de llamadas a funciones, pero eso es harina de otro costal). Este mecanismo se llama “categorías“, y consiste en agregar nuevos métodos a una clase existente. Este método puede acceder a atributos privados (en Objective-C no existen atributos protegidos, y cualquier método puede ser invocado siempre que se conozca su cabecera). Lo más destacable de este mecanismo es que no hace falta realizar un casting al objeto, sino que automáticamente la clase adquiere los métodos de las categorías a las que tiene acceso (incluidas en un fichero de cabecera previo). Pueden imaginar la de plugins que pueden hacerse con esto, y lo limpio que queda el código. El único cuidado que hay que tener es el de documentar bien el código a fin de no confundir métodos estándar de categorías propias.

Traducción de recursos

La traducción de cadenas de textos en Qt está muy documentada, pero no me pareció lo mismo al tratar de incorporar soporte multi-idioma en los recursos de las aplicaciones. Así que acá va. Imaginemos que tenemos una imagen “:/hola.png” que queremos traducir. Nuestro QRC originalmente contendrá algo tal que:

<qresource>
 <file>hola.png</file>
</qresource>

Para agregar las traducciones, debemos crear alias a dicha imagen desde varios ficheros (en el ejemplo, el castellano es el idioma por defecto):

<qresource>
 <file alias="hola.png">hola_es.png</file>
</qresource>
<qresource lang="en">
 <file alias="hola.png">hola_en.png</file>
</qresource>

A modo resumen, para traducir un texto debemos borrar el traductor anterior de la aplicación (si lo hubiera), crear un nuevo traductor desde nuestro fichero QM (fichero compilado de traducciones), instalar el nuevo traductor, re-traducir todas nuestras interfaces abiertas mediante “::retranslateUi()” (las ventanas sin abrir usarán la nueva traducción automáticamente al crearlas), y volver a generar los textos que programáticamente hayamos generado.

Con los ficheros de recursos debemos cambiar el parámetro global de localización de la aplicación antes de re-cargar los recursos. Para ello:

QLocale::setDefault(QLocale("en"));

Donde “en” es el código de localización deseado (“es”, “en”, “de”, “it”, etc).

Una sugerencia personal si queremos que el idioma cambie dinámicamente (sin tener que cerrar y volver a abrir nuestra aplicación) es crear una señal que se emita desde la configuración del idioma después de haber configurado el nuevo traductor y la localización, y que reciban todos los objetos que deban traducir sus textos o recursos.

Hasta la próxima, ¡espero disfruten de los bocaditos!

Qt + resources + fonts + stylesheets

A partir de esta entrega cambiaré el título de las anec-notas para reflejar mejor la temática y ahorrar espacio en la cabecera de la entrada: muchas veces estará en inglés (por eso de ser el idioma universal de la programación), quitaré el “anec-notas” del mismo y dejaré la indicación únicamente en la categoría del post.

Terminado este preámbulo, esta entrega mostrará una forma de incluir fuentes propias en un proyecto Qt, al menos la que más me gusta. Lo primero será ubicar el fichero de fuentes. Personalmente recomiendo utilizar un fichero de recursos (QRC) en el cual embeber la fuente:

<RCC>
 <qresource>
 <file>fuentes/mipropiafuente.ttf</file>
 </qresource>
</RCC>

Esta fuente deberá estar ubicada en “./fuentes/” respecto al fichero de recursos.

Antes de poder utilizar una fuente, hay que añadirla a la base de datos de fuentes de la aplicación. Para ello se usa la siguiente llamada:

QFontDatabase::addApplicationFont(":/fuentes/mipropiafuente.ttf");

A partir de este momento la fuente estará disponible en toda la aplicación. Bastará con especificarla en una hoja de estilos:

QWidget
{
 font-family: "Nombre real de nuestra fuente";
 font-size: 14px;
 color: #999999;
}

O directamente desde código:

// "pt" es un "QPainter *", seguramente un argumento del evento paint(...)

QFont fold(pt->font());
QFont fnew(fold);

fnew.setFamily("Nombre real de nuestra fuente");
fnew.setWeight(QFont::Bold);
pt->setFont(fnew);

// Escribir texto

pt->setFont(fold); // restablecer la fuente

Sugerencias finales sobre los stylesheets

Para agilizar la personalización de la interfaz sugiero cargar el fichero de estilos por fuera del fichero de recursos durante la etapa de desarrollo, y únicamente cargarlo desde el QRC cuando se pase a producción.

Además, si se están cambiando ficheros ya incluidos en el QRC, tales como imágenes, habrá que saber que los recursos de Qt sólo se re-compilan cuando el fichero .qrc es modificado, no cuando los ficheros incluidos lo hacen. Una buena técnica para automatizar este proceso es “tocar” el fichero QRC antes de cada compilación, al menos durante las pruebas de diseño de interfaz (la compilación de un QRC puede tomar su tiempo si los recursos incluidos son grandes). Para ello sugiero ver la entrada anterior sobre el comando touch en VisualStudio.

Anec-notas (IV)

Parece contradictorio que durante las vacaciones esté publicando más posts sobre mi trabajo que durante los meses laborales, pero qué le vamos a hacer, la vida es así.

Esta entrada la quiero dedicar al Visual Studio, así que allá vamos.

Implementar el comando “touch”

Unos de esos comando de UNIX que siempre me han gustado es el touch (para los que no lo conozcan, este comando modifica la cabecera de un fichero actualizando su fecha de modificación, creando el archivo si éste no existiere). Windows no tiene un comando equivalente a primera mano. Buscando un poco me encontré que se puede emular usando

copy /b <fichero> +,,

Donde es el nombre del fichero a “tocar” (pueden usarse comodines). Una limitación de este comando es que el fichero debe estar en el mismo directorio que el activo desde donde se ejecuta el comando. Ejecutar el comando siguiente no funcionará correctamente sino que copiará el fichero al directorio actual.

copy /b ..\config\game.ini +,,

La línea correcta en ese caso sería

copy /b ..\config\game.ini +,, ..\config\game.ini

Ahora bien, ¿a qué todo esto en Visual Studio? Imaginemos el siguiente escenario: estamos utilizando Qt y tenemos una hoja de estilos definida en un fichero QSS (Qt Stylesheet), el cual está incluido como recurso en un fichero QRC. Este fichero se recompila únicamente cuando se ha modificado, no cuando se han modificado los ficheros que incluye. Por lo que si estamos modificando continuamente el fichero de estilos tendríamos que “modificar” el fichero QRC para que se recompilase. En este caso el comando touch es un gran aliado. Para automatizar esta tarea podemos recurrir a los “Build Events”, más específicamente al “Pre-Build Event”. Está ubicado en “Project Properties > Build Events > Pre-Build Event”, donde especificamos el comando a utilizar (el ampersand “&” es un separador de comandos para poder indicar varios comandos uno a continuación de otro):

cd Resources &amp; copy /b *.qrc +,,

Los “Build Events” sólo permiten ejecutar un único comando (línea), por lo que si queremos realizar varias tareas lo mejor será definir un fichero por lotes que las ejecute. Otro detalle a tener en cuenta es que los ficheros QRC pueden tardar cierto tiempo en compilarse si contienen muchos recursos, especialmente imágenes, por lo que es conveniente que este evento sólo esté presente en una configuración de proyecto, tal como la Debug, o crearnos una especial.

Detalles de usabilidad

Acá me gustaría criticar comentar un detalle en la pantalla de creación de nuevos proyectos.

Nuevo proyecto VS

Resulta que en los campos de “Nombre” y “Nombre de la solución”, este cuadro de diálogo comprueba si el texto introducido es ““, en cuyo caso desactiva el botón “OK”. Probad vosotros y borrad una letra para que veáis cómo podéis crear el proyecto. Re-escribid esa letra y el botón “OK” se desactivará. ¿Tantos avances que se han hecho en usabilidad y no podían usar la propiedad “placeholder” en este caso?

Anec-notas (III)

Las vacaciones no siempre son vacaciones al 100%, cuando se trabaja en el mundo informático lo normal es pasear por todo el país con el portátil a cuestas y tener que realizar cambios sobre la marcha o pequeños ajustes a algún proyecto. Y dado que ya estaba en ésas he acá la tercera entrega de anéc-notas.

QImage

Entre los ajustes que he tenido que hacer hay uno que se refiere a la adquisición de imágenes a 60fps desde diferentes modelos de sensores. El flujo de datos involucra la visualización de dichos fotogramas en una interfaz programada en Qt. Sin entrar en más detalles, la imagen la adquiría en un QImage para su representación. Luego, con el fin de procesarla con otros algoritmos intermedios, obtenía el puntero al bloque de datos mediante un QImage::bits(). De ahí surgen los dos comentarios de esta sección.

El primero es que dicho arreglo de bytes puede contener un padding final, por lo que los píxeles de una fila no están a continuación de la anterior, sino después de un pequeño espacio. Esto no ocurre si la imagen ha sido creada desde un puntero (lo cual evita hacer una copia del bloque de memoria), pero si la imagen se manipula o se crea de otra forma hay que tener en cuenta este espacio.

El otro detalle me pareció muy curioso. Este método tiene dos variantes, una “const” y otra “no-const”. La diferencia principal es que la variante “no-const” crea una copia antes de devolver el puntero, por lo que cualquier optimización que uno crea que está haciendo al acceder directamente al bloque de memoria puede irse al pozo si no tenemos cuidado, por ejemplo, si hacemos algo como:

swap_rgb_to_gbr(img.bits());

Y la función es algo de este estilo:

void swap_rgb_to_gbr(unsigned char *pixels);

La mejor solución que he podido encontrar implica forzar un casting:

const unsigned char *buffer = img.bits();
swap_rgb_to_gbr(const_cast&lt; unsigned char* &gt;(buffer));

Ahora bien, cuando se trata de cambiar el tamaño de una imagen, QImage es poco más que un niño de 2 años con unos lápices de colores en la mano. Lo mejor es utilizar opciones más potentes, como por ejemplo OpenCV (asumiremos que src es ya una imagen de OpenCV válida):

cv::Mat dst;
cv::resize(src, dst, cv::Size(nuevo_ancho, nuevo_alto), 0.0, 0.0, cv::INTER_LANCZOS4);

QImage resizedimg(nuevo_ancho, nuevo_alto, QImage::Format_RGB888); // 24bits por píxel

const unsigned char *dstdata = dst.data;
const int rowsize = nuevo_ancho * 3; // RGB
for (int ii = 0; ii &lt; nuevo_anchoe; ++ii, dstdata += rowsize)
 memcpy(resizedimg.scanLine(ii), dstdata, rowsize);

QMenu

En lo particular me gusta tener un menú “Debug” en el cual puedo incorporar funcionalidades de depuración o que sólo deban estar presente durante el desarrollo, tales como recargar el fichero de estilos, por ejemplo. A fin de simplificar el desarrollo, este menú está siempre presente, y si la aplicación no está siendo compilada en modo depuración se quita.

Hago mención de dicho proceso acá porque no me fue evidente encontrar el cómo se hacía, así que lo dejo por escrito.

ui.menubar-&gt;removeAction(ui.menuDebug-&gt;menuAction());

Otro detalle sobre los menús es cómo establecer su color de fondo y de texto. Acá dejo un stylesheet de ejemplo.

/* Barra de menu */
QMenuBar
{
 background-image: url(:/someimage.jpeg);
}

/* Elementos de la barra de menú: título de cada menú */
QMenuBar::item
{
 background-color: transparent;
 color: #999999;
}
QMenuBar::item:selected
{
 background-color: transparent;
 color: #ffffff;
}

/* Cada menú */
QMenu
{
 background-color: #444444;
 color: #999999;
}

/* Cada acción del menú */
QMenu::item
{
 background-color: #444444;
}
QMenu::item:selected
{
 color: #ffffff;
}

Anec-notas (II)

Esta semana ha sido bastante movida ya que tenemos la entrega de un producto para la semana que viene, ya sabéis, afinar detalles y esas cosas, así que en esta segunda emisión de Anec-notas tendremos un par de puntos prácticos sobre Qt.

QLineEdit

El control QLineEdit es probablemente de los más utilizados a la hora de crear formularios. Normalmente se fija su contenido con un QLineEdit::setText(QString) al cargar el formulario, y se lee de vuelta su valor con un (QString)QLineEdit::text() al cerrar o validar el mismo. Lo que ocurre en este caso es que el flujo de la aplicación no establece puntos “finales” claros en los que validar u obtener el contenido del control (es decir, no hay un botón “Aceptar” o “Guardar”), sino que debe hacerse continuamente (cada vez que hay un cambio), guardando el contenido a la base de datos.

Con este fin es posible utilizar las señales textChanged(QString) y textEdit(QString), que se emiten cada vez que el contenido del control es modificado. La principal diferencia entre ellas radica en que textEdit sólo se emite cuando el contenido del control es modificado desde la interfaz (por el usuario), mientras que textChanged es emitida también si se cambia programáticamente mediante setText(QString). Para evitar que cada vez que inicialice el control tenga que escribir en la base de datos (operación innecesaria ya que el contenido en ese momento es el mismo), se utiliza la señal textEdit(QString).

Menús contextuales

Cuando se despliega un menú contextual, éste se apropia del mouse (grabMouse) lo que evita que los widgets que han solicitado seguir continuamente el movimiento del cursor no reciban el evento mientras el menú contextual esté desplegado. Como ejemplo práctico, imaginemos un widget que renderiza elementos gráficos propios, como un widget de OpenGL, y que mediante eventos MouseMove cambia el estado “highlighted” de los elementos que están debajo del cursor. Al desplegar el menú contextual para ese elemento, el widget deja de recibir el evento de MouseMove, por lo que cuando se oculta el menú contextual, el mouse puede estar en otra posición, resultando en una incoherencia entre el elemento resaltado y el cursor.

Los menús tienen una señal, aboutToHide(), que se emite cuando el menú se oculta, bien sea por seleccionar una entrada del menú o por desactivar el menú. Con esta señal se puede actualizar el estado de los elementos forzando un evento MouseMove. Ahora bien, este evento se emite antes de llamar al método asociado a la entrada del menú, por lo que si éste necesita del estado actual (por ejemplo, el elemento resaltado) debe tenerse este hecho en cuenta.

Doxygen + namespaces + Qt

Desde hace algunos días me estoy dedicando a documentar unas bibliotecas para C++ que he desarrollado en mi trabajo. Para ello, como no, estoy utilizando Doxygen. Como utilizamos CMake para generar los proyectos, pues qué mejor lugar para generar la documentación. Muestro a continuación la configuración, simplemente a modo de ilustración:

#-- Add an Option to toggle the generation of the API documentationOPTION(BUILD_DOCUMENTATION "Use Doxygen to create the HTML based API documentation" ON)IF(BUILD_DOCUMENTATION)FIND_PACKAGE(Doxygen)IF(NOT DOXYGEN_FOUND)MESSAGE(FATAL_ERROR "Doxygen is needed to build the documentation. Please install it correctly.")ENDIF()#-- Configure the Template Doxyfile for our specific projectCONFIGURE_FILE(Doxyfile.in ${PROJECT_BINARY_DIR}/Doxyfile @ONLY IMMEDIATE)FILE(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/doc)#-- Add a custom target to run Doxygen when ever the project is builtADD_CUSTOM_TARGET(DocsCOMMAND ${DOXYGEN_EXECUTABLE} ${PROJECT_BINARY_DIR}/DoxyfileSOURCES ${PROJECT_BINARY_DIR}/Doxyfile)# IF you do NOT want the documentation to be generated EVERY time you build the project# then leave out the 'ALL' keyword from the above command.ENDIF()

El fichero Doxyfile.in contiene la siguiente configuración:

PROJECT_NAME = @LIBRARY_NAME@PROJECT_NUMBER = @LIBRARY_VERSION_MAJOR@.@LIBRARY_VERSION_MINOR@.@LIBRARY_VERSION_PATCH@OUTPUT_DIRECTORY = "@PROJECT_BINARY_DIR@/doc"STRIP_FROM_PATH = "@PROJECT_SOURCE_DIR@/lib"INPUT = "@PROJECT_SOURCE_DIR@/lib"RECURSIVE = YESCREATE_SUBDIRS = YESEXTRACT_PRIVATE = NOGENERATE_TODOLIST = YESGENERATE_BUGLIST = YESWARNINGS = NOWARN_IF_UNDOCUMENTED = NO

Donde @LIBRARY_NAME@ y demás son variables de entorno definidas en el fichero CMake y que hacen alusión al nombre de la biblioteca y la versión actual de la misma.

No entraré en más detalle sobre el uso de Doxygen ya que hay mucho escrito, salvo por un pequeño “problema” que tuve.

Resulta que si generamos la documentación a palo seco, los namespaces que hayamos utilizado serán incluidos en la documentación. Esto está bien hasta cierto punto: si la documentación es de una API o de una biblioteca, es muy común que todo esté dentro de un namespace común (por ejemplo, libname). En este caso toda la documentación estará plagada de libname::, libname::, libname::… y no es precisamente agradable a la vista ni fácil de leer.

Buscando en Google, rápidamente di con una posible solución (como verán, la he adaptado para incluir mi versión, pero la seguiré contando acá de todas formas):

http://stackoverflow.com/questions/6811816/doxygen-strip-top-level-namespace/11231222

El código propuesto consiste en definir una serie de macros, y reemplazar el uso del namespace principal por ellas.

#ifndef DOXY_PARSER#define LIB_NAMESPACE_STARTS namespace lib_namespace { /##/#define LIB_NAMESPACE_ENDS } /##/#define LIB_NAMESPACE lib_namespace#else#define LIB_NAMESPACE_STARTS /##/#define LIB_NAMESPACE_ENDS /##/#define LIB_NAMESPACE#endif

Con esta solución hay un problema, si usamos Qt en el proyecto (como podrán suponer era mi caso), el proceso de generación de los ficheros moc se salta por completo las macros (no las expande), y por ende el fichero .cxx generado no dispone de la información de los namespaces, lo cual, adivinaron, finaliza en un montón de errores por parte del comilador.

Una posible solución sería utilizar un #define QT_NAMESPACE lib_namespace, pero esto movería todo el espacio de nombres de Qt al de la biblioteca, y eso no es bueno.

La solución que encontré al final es más general, y funciona tanto en proyectos que utilizan Qt como los que no.

#ifndef DOXY_PARSER#define LIB_NAMESPACE lib_namespace#define LIB_NAMESPACE_STARTS namespace LIB_NAMESPACE { /##/#if (defined MOCED_FILE)#define LIB_NAMESPACE_ENDS } using namespace LIB_NAMESPACE; /##/#else#define LIB_NAMESPACE_ENDS } /##/#endif#define USING_LIB_NAMESPACE using namespace LIB_NAMESPACE; /##/#else#define LIB_NAMESPACE_STARTS /##/#define LIB_NAMESPACE_ENDS /##/#define LIB_NAMESPACE#define USING_LIB_NAMESPACE /##/#endif

Donde `MOCED_FILE` es una macro definida únicamente para los ficheros moc. Esto es sencillo si se utiliza CMake:

QT4_WRAP_CPP(mocSources qt_file1.h qt_file2.h)SET_SOURCE_FILES_PROPERTIES(${mocSources} PROPERTIES COMPILE_FLAGS "-DMOCED_FILE")

Si bien la intención original del post era comentar mis andanzas con este problema de los namespaces, decidí agregar una pequeña introducción al uso de Doxygen con CMake ya que por la red no lo encontré a la primera.