LinuxQuestions.org
Download your favorite Linux distribution at LQ ISO.
Go Back   LinuxQuestions.org > Blogs > rainbowsally
User Name
Password

Notices

Rate this Entry

Tooling Up. ui-tool (QT4 ui file utilites)

Posted 12-15-2012 at 10:16 AM by rainbowsally
Updated 06-10-2013 at 04:17 PM by rainbowsally (bullet list not showing correctly + typo fix + parag alignment)

UPDATE: (2013-06-10) There's a stand-alone version of the uitool here.
http://www.linuxquestions.org/questi...3-06-10-35568/

It's part of the next libLQ-qt/mc2 project but that lib is not ready to fly yet.

--------
Note: This is now part of the libLQ-qt download, but it's been renamed lq-uitool. It builds in the tools folder and should install automatically if all goes well.

But the app itself doesn't require the libs. It will 'make' and run without the libs or mc2, if that's all you want in the mc2 dload. I.e., The makefile exists. After that mc2 is no longer needed.
----------------------------------------

Tooling Up.

This tool is worth the effort for the 'sortSlots' function alone. That can be very helpful if you are working on a large project in QT Designer and you need to find the names of the slots you have already set up.

Today's feature
  • A commandline tool called 'ui-tool'
  • It will be a simple interpreter with a dictionary, and help strings.
  • It will avoid using perl or any other external application to generate linkage between names, help text, and an executable function.
  • It will use 'extern "C"' to avoid C++ excessive type safety while retaining the features of C++ that we want.

This is in two parts. The main implementation code (and likely useful snippets) are in the code linked at the bottom of this page. Or just page back one to see it. ;-)

The list of functions it will handle be as follows.

Code:
ui-tool commands (minus dashes). $UIFILE is the name of the 
qt4 ui file.

  sortSlots $UIFILE
    sorts the slots in a ui file for easier lookups of slots 
    in the signal slot editor

  writeCpp $UIFILE
    writes slot templates for the file named like the $UIFILE
    but with the '.cpp' file extension.
    
  writeH $UIFILE
    writes slot templates for the file named like the $UIFILE
    but with the '.h' file extension.

  listSlots $UIFILE
    Shows slots found in the $UIFILE

  listSignals $UIFILE
    Shows signals found in the $UIFILE

  commands
    shows this list and usage notes.

  help
    Displays a simple help message
The above is what will be printed out when the user types
Code:
ui-tool commands
[Note that 'commands' does not have the dash prefix. That's because it's an interpreter, though it will strip the dashes if you prefer the 'switch' syntax.]

Now let's talk about some of the features of the build system. We've talked enough about mc2 and using the folders as project organization (like java does) so let's now talk about code organization.

First of all, it doesn't really matter whether stuff is upper or lower case or has underscores to separate words as far as the compiler is concerned. All the compiler worries about is whether or not the names are unique and whether it can find enough of them to output a binary file.

That said, why not use a 'macro' to define various things such as enums, string names, and in this case, a jump table.

Here's the file we use to keep all the structs and definitions in sync.

file: src/def_loader.h
purpose: define and sync definitions at compile time
Code:
// the stuff at the top is TODO still
//DEF(changeWidgetName, 3)
//DEF(changeSlotName, 3)
//DEF(changeSignalName, 3)
//DEF(changeClass, 2)
/* using DOS style wildcards */
//DEF(find, 2) 
//DEF(findSlot,2)
//DEF(findSignal,2)

DEF(sortSlots, 1)
DEF(writeCpp, 1)
DEF(writeH, 1)
DEF(listSlots, 1)
DEF(listSignals, 1)
DEF(commands, 0)
DEF(help, 0)
The header does not itself define the DEF macro. As a result, it can take the two parameters of the macro and do any number of different things with the same two parameters.

For example, here we define the dictionary as well as a couple of functions needed to access it.

file: src/dict-util.cpp
purpose: source file
Code:
// dict.cpp

/* This file contains the lookup and execution functions as well 
 * as loading the dictionary with the commands, the number of 
 * parameters each requires and the help strings.
 */

#include <string.h>  // strcasecmp()
#include <stdio.h> // printf()
#include "dict-util.h" 
#include "ui-tool.h"
#include "help.h"

extern "C"
{
dict_t dict[] =
{
  // Here's all there is to it.
#define DEF(name, nParams) {#name, (func_t)name, nParams, name##_help},
#include "def_loader.h"
#undef DEF
  // and the dictionary is loaded and all fields are defined.
  0,0
};
  
// We allow case insensitive function name lookups
#define STREQ(a, b) (!strcasecmp(a, b))

// Internal: returns index or -1 if not found
int find_command(const char* cmd)
{
  for(dict_t* p = dict; p->name != 0; p++)
  {
    if(STREQ(p->name, cmd))
      return p - dict;
  }
  return -1;
}

// Internal: executes the command at index (like a byte code)
// with the args from the commandline (not like byte code).
void do_cmd(int cmd_index, char** args)
{
  bool ret = false;
  dict_t* p = dict+cmd_index;
  func_t e = p->func;
  int nparams = p->argc;
  ret = p->func(args[1], args[2], args[3], args[3]);
/*  
  switch(nparams)
  {
    case 0:
      ret = p->func();
      break;
    case 1:
      ret = p->func(args[1]);
      break;
    case 2:
      ret = p->func(args[1], args[2]);
      break;
    case 3:
      ret = p->func(args[1], args[2], args[3]);
      break;
    case 4:
      ret = p->func(args[1], args[2], args[3], args[3]);
      break;
    default:
      ret = false;
  }
  */
  if(!ret)
    fprintf(stderr, "Unknown error occurred in execution of '%s'\n", p->name);
}
  
void show_command_help()
{
  const char* commands_str =
      "ui-tool commands (minus dashes). $UIFILE is the name of the \n"
      "qt4 ui file.\n"
      "\n";
  printf(commands_str);
  for(dict_t* p = dict; p->name != 0; p++)
    printf(p->help);
}
 
} // extern "C"
You might think that the 'big' part of this program is find_command, do_command, and show_command, but in fact it's that little piece of code that does this (below) that does all the heavy lifting.

Code:
#define DEF(name, nParams) {#name, (func_t)name, nParams, name##_help},
#include "def_loader.h"
#undef DEF
Even beginners with C will know what a '#define' does, but the other stuff might be worth explaining.

[#name] puts quotes around the text represented by "name" in the macro. This is therefore the 'name' field of the 'dict_t' struct.

[(func_t)name] tells the compiler to interpret 'name' as a 'func_t' so it will match the struct definition for the call address/jump able entry, or whatever you want to call it. That's the field that will be executable.

[nParams] is unused in this application, but it's the number of parameters required by the executable code. This field might come into play if this were a more fully blown interpreter where the various parts had to talk to each other.

And here's the pizza day resistance.

[name##_help]

As we know the stuff prefixed by a number sign '#' in C is a preprocessor directive. What we might not know is that, as such, everything is still dealt with as text. So what this double hash does is join the two strings "<name>" (defined in the macro's input parameter) and "_help" (defined in the definition body of the macro) so that the resulting strings are <name>(+)_help" and will match the names in this file (below), where the help strings are all defined.

file: src/help.cpp
purpose: source file
Code:
#include "help.h"

/* 
 * These are the help fields for each command
 */

const char* sortSlots_help =
  "  sortSlots $UIFILE\n"
  "    sorts the slots in a ui file for easier lookups of slots \n"
  "    in the signal slot editor\n"
  "\n";

const char* writeCpp_help =
  "  writeCpp $UIFILE\n"
  "    writes slot templates for the file named like the $UIFILE\n"
  "    but with the '.cpp' file extension.\n"
  "    \n";

const char* writeH_help =
  "  writeH $UIFILE\n"
  "    writes slot templates for the file named like the $UIFILE\n"
  "    but with the '.h' file extension.\n"
  "\n";

const char* listSlots_help =
  "  listSlots $UIFILE\n"
  "    Shows slots found in the $UIFILE\n"
  "\n";

const char* listSignals_help =  
  "  listSignals $UIFILE\n"
  "    Shows signals found in the $UIFILE\n"
  "\n";

const char* help_help =
  "  help\n"
  "    Displays a simple help message\n"
    "\n";

const char* commands_help =
  "  commands\n"
  "    shows this list and usage notes.\n"
  "\n";
And so that's how we get the nifty help listing for all the commands and what they do.

[the LQ-qt-mc2 download includes the tool used to generate these strings from a text file. See examples and find the 'txt2c' files, probably in a 'bin' folder because I think that was part of a MULTI build demo.]

Another file worth mentioning here is the header for the code above.

It needs to make public the names of the strings defined in the c++ file above.

Sounds like it might be a lot of typing, right?

Well....

file: src/help.h
purpose: source file
Code:
// the help fields for each command
#ifndef help_h
#define help_h

#define DEF(name, n) extern const char* name##_help;
#include "def_loader.h"
#undef DEF

#endif // help_h
Notice that the macro ignores the 'n' parameter in the macro input. It only wants the name field for declaring the names as externals so that the compiler can find the names when filling in the jump table and also so it doesn't think the string is being defined when the header loads.

The 'extern' linkage (as it's called by the compiler) tells the compiler to put this definition "somewhere else". It must be defined (and it is defined in the c++ file), but it doesn't matter where it's defined until 'link' time.

It could be in a separate source file, a static library, or it even could be "resolved" at run-time if it happens to be a shared object ("*.so") aka a dynamically loaded library.

But the main point here is:

Same header, different macro definitions so everything remains sync-ed up due to the single source file that loads EVERY time we recompile and will generate errors if anything's missing or renamed.


Here are some more of the files for this application.

file: src/dict-util.h
purpose: defines the dictionary linkage fields/jump table parameters
Code:
// getting down low..  
#ifndef dict_util_h
#define dict_util_h

extern "C" 
{
  // the main utilities
  int find_command(const char* cmd);
  void do_cmd(int cmd_index, char** args);
  void show_command_help();

  // how calls are performed
  typedef bool (*func_t)(...);
  
  // what can be looked up
  typedef struct
  {
    const char* name;
    func_t func;
    int argc;
    const char* help;
  }dict_t;

  // the dictionary with function and help linkage
  extern dict_t dict[];
} // extern "C"

#endif // dict_util_h

file: src/main.cpp
purpose: the "interpreter" such as it is, not an interpreter loop, however.
Code:
// main.cpp -- skeleton created by new.main

/*
 *   sortSlots \$UIFILE
 *    sorts the slots in a ui file for easier lookups of slots 
 *    in the signal slot editor
 * 
 *  writeCpp \$UIFILE
 *    writes slot templates for the file named like the $UIFILE
 *    but with the '.cpp' file extension.
 *    
 *  writeH \$UIFILE
 *    writes slot templates for the file named like the $UIFILE
 *    but with the '.h' file extension.
 * 
 *  listSlots \$UIFILE
 *    Shows slots found in the \$UIFILE
 * 
 *  listSignals \$UIFILE
 *    Shows signals found in the \$UIFILE
 * 
 *  commands
 *    shows this list and usage notes.
 * 
 *  help
 *    Displays a simple help message in a message box
 */

#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <QApplication>
#include "ui-tool.h"
#include "dict-util.h"  // find and do_cmd

void dbg(){} // for a non-moving breakpoint

// returns index or -1 if not found
int find_command(const char* cmd);
void do_cmd(int cmd_index, char** args);

int main(int argc, char** argv)
{
  dbg();
  
  new QApplication(argc, argv); // creates global qApp  
  
  if(argc < 2)
    return usage(1);
  
  char* cmd;
  cmd = strrchr(argv[1], '-');
  
  if(!cmd)
    cmd = argv[1];
  else
    cmd++;
        
  int cmd_index = find_command(cmd);
  if(cmd_index < 0)
  {
    fprintf(stderr, "Can't find the command '%s' in my dictionary\n", cmd);
    return 1;
  }
  do_cmd(cmd_index, argv + 1);
  
  return 0;
}
Here's the header for the brains of the application. This is still under development. The internals below should probably be excluded from this header and made 'static' in the source file when it's done.

file: src/ui-tool.h
purpose: public declarations for ui-tool.cpp
Code:
// ui-tool.h - template created with new.header

#ifndef ui_tools_h
#define ui_tools_h

#include <LQ-qt/SList.h>

extern "C"
{
int usage(int errcode);
// internal
bool moveSlots(SList* pslots, SList* pxml);
bool insertSlots(SList* pxml, SList* pslots);
// normal
bool sortSlots(const char* filename);
bool writeCpp(const char* filename);
bool writeH(const char* filename);
bool listSlots(const char* filename);
bool listSignals(const char* filename);
void printVersion();
bool commands();
bool help();

} // extern "C"

#endif // ui_tools_h
Note that these are declared as extern "C" in order to avoid C++ name mangling. The names in the asm output (add '--save-temps' to the CFLAGS if you want to see the asm code) will be identical to the names as shown above.


file: src/uitool_str.txt
purpose: text file source for generating C string source
Code:
Usage: ui-tool <command> [param(s)]
  Commands can have one, two, or zero dash prefixes.
  For a list, type
    ui-tool commands
This help message is currently shown in a message box (a worthy snippet in the c++ file). But I don't like the messag box because it doesn't preserve the indentation. But it's ok for now.

Next is the output from the above text file.

Note that the extension is just about anything EXCEPT c, cpp, cxx, or whatever, because mc2 would think it's a source file to compile.

That would be ok, if we wanted the string defined globally, but we want it as a local var so the file is #included in the body of the help function in the ui-tool.cpp file.

file: src/uitool_str.dat
purpose: source file
Code:
// This file was created with txt2str
const char* uitools_str =
  "\n"
  "Usage: ui-tool <command> [param(s)]\n"
  "  Commands can have one, two, or zero dash prefixes.\n"
  "  For a list, type\n"
  "  ui-tool commands\n"
  "\n"
  "\n"
;
And because this is under development and it's a pain to continually be opening a terminal which IS MUCH nicer in konqueror than dolphin (check it out if you don't know this) we want to simply click on something and get the strings updated from the text file.

file: src/update-strings.exec
purpose: utility (executable)
Code:
#!/bin/sh
cd `dirname "$0"`
printf "" >.msg
txt2str uitool_str.txt uitool_str uitool_str.dat
#txt2str commands_str.txt commands_str commands_str.dat
echo 'done.' >> .msg
kdialog --msgbox "$(<.msg)"
rm -f .msg
Add 'touch *' above to get the makefile to recognize changes, if you like. Also if you have built the MsgBox app (a few pages back) you could replace the kdialog line with
Code:
MsgBox -i "$(<.msg)"
which you might like better. Or not. But you have options.

The remaining files needed to create this application are here (see link below).

http://www.linuxquestions.org/questi...ols-cpp-35206/

[We will update the code for a while if bugs are found in this code so far. Initial testing looked good, but we had to upload this before more experimental features got added in.]

- The Computer Mad Science Team

:-)
Posted in Uncategorized
Views 553 Comments 0
« Prev     Main     Next »
Total Comments 0

Comments

 

  



All times are GMT -5. The time now is 09:01 AM.

Main Menu
Advertisement

My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration