Close

Porting a Visual Basic Excel Program to C and Zenity

A project log for Using Zenity for GUI Command Line Programs (in C)

I need a very simple GUI for my command line programs. Pipes and Zenity provide a reasonable solution.

agpcooperagp.cooper 02/18/2022 at 04:560 Comments

Boat Offset Table

I have an DeltaCAD macro for importing Boat Offset Tables from Excel as a CSV file. The macro creates 3D DXF models, unfolded panels and various sections.

The problem is that DeltaCad is Windows only and is not 100% WINE compatible (no macro support).

I ported the macro to Excel but I update my computer every few years so keeping a copy of Excel for Linux is a problem.

Thus I am slowly migrating my code to "C". I have already written a DXF Out header library to export a "primitive" version of DXF. Therefore I use QCad or DeltaCad to import my DXF files.

What I need is a simple GUI to input files names and parameters. (In the past I used a batch file, or xForms as a full featured GUI). Zenity is an acceptable GUI interface (not perfect but good enough).


Here is the Zenity function:

char** zenity(char* cmd,int n)
{
  // create string array
  char** str=calloc(n+1,sizeof(char*));
  for (int i=0;i<=n;i++) *(str+i)=(char*)calloc(256,sizeof(char));
  char line[256]={0};

  // Run Zenity
  FILE* fp=popen(cmd,"r");
  if (fp==NULL) {
    // Error
    perror("Pipe returned a error");
    exit(0);
  } else {
    fgets(line,sizeof(line),fp);
    sprintf(*str,"%d",WEXITSTATUS(pclose(fp)));
  }

  // Remove non-characters
  for (int i=0;(line[i]>0);i++) if (line[i]<32) line[i]=0;
  int ptr=1;
  char* token=line;
  for (int i=0;(line[i]>=32);i++) if (line[i]==',') {
    line[i]=0;
    strcpy(*(str+ptr),token);
    ptr++;
    token=&line[i+1];
  }
  strcpy(*(str+ptr),token);

  return str;
}

It is short and to the point. The main limitation is that the maximum line length is 256 characters (including the end of line marker).

Here is an example of use:

  // Test if Zenity is installed (get version)
  if ((fp=popen("zenity --version","r"))!=NULL) {

    // Get CSV Input File
    parameters=zenity("zenity --file-selection \
      --file-filter='Boat Offsets *.csv' \
      --title='Select a CSV File' \
      ",1);
    if (atoi(*parameters)==1) {
      // Cancel
      exit(0);
    } else {
      strcpy(offsetNamePath,*(parameters+1));
    }

    // printf("str ptr %ld\n",(long)parameters);
    // for (int i=0;i<=1;i++) {
    //   printf("%s %ld\n",*(parameters+i),(long)*(parameters+i));
    // }
    for (int i=0;i<=1;i++) free(*(parameters+i));
    free(parameters);
    ...

The first zenity call "zenity --version" checks if zenity is installed.

	if ((fp=popen("zenity --version","r"))!=NULL) {

 
The second zenity call "zenity --file-section ..." gets a CSV file for processing:

    parameters=zenity("zenity --file-selection \
      --file-filter='Boat Offsets *.csv'
      --title='Select a CSV File' \
      ",1);

The last parameter of the zenity call is 1, meaning only one parameter is returned, accessed as string "*(parameter+1)". Actually, two parameters are returned, the exit code is accessed as the string "*parameter". This code prints out the parameters:

    for (int i=0;i<=1;i++) {
      printf("%s\n",*(parameters+i));
    }

Finally, when your finished with the parameter, you should free the memory space with:

    for (int i=0;i<=1;i++) free(*(parameters+i));
    free(parameters);

What does the GUI look like?

Here is the file selection GUI:

And here is the Options form:

The only problem with the form is that I cannot populate the entry boxes with default values.

Reading Comma Separated Values (CSV) in C

Reading CSV files is quite difficult as CSV is not really a standard. How should we read:

1,"hello, ",,  A  B  ,

1 Is the second comma part of the string of a separator?

2 How should we deal with leading spaces?

3 How should we deal with embedded spaces?

4 How should we deal with trailing spaces?

5 How should we deal with multiple delimiters?

6 How should we dealt with end of line characters?

My answer is that the embedded , is illegal and the ,, should read a NULL string.

What is the problem with C input?

Consider this fscanf(F1,"%d,%s,%s,%s",&i,str1,str2,str3); to import a line from a file. Note I don't care for the last value after str3. The %d will work fine, the next %s with read the remainder of the line!

The problem is the %s. C does provide a way out:

  fscanf(F1,"%d,[^,],[^,],[^,]",&i,str1,str2,str3);

The code [^,] is a %s replacement but uses , as a deliminator. This format code is very powerful but I am not going to explain its full use here. Note, we still need to read the , with the format string with the trailing ,.

But there are still problems. The two consecutive ",," are read as one ",". We can fix this with %*c. This format reads any character (i.e. the ,) but does not (i.e. the *) use the character:

  fscanf(F1,"%d,[^,]%*c[^,]%*c[^,]",&i,str1,str2,str3);

This works as expect but on the next file read, we find we are still processing the last line! So in order to fscanf() we have to fully specify the line we want to read! We could just use a %s after the last format code to clean up anything remaining but is not very elegant. Few people would know why the extra %s is sometimes necessary.

I think a better solution is to read the file line separately with

  fgets(line,sizeof(line),F1);

and then process the line with:

  sscanf(line,"%d,[^,]%*c[^,]%*c[^,]",&i,str1,str2,str3);

AlanX

Discussions