Groups and Items

In the previous section, we built a trivial KFile plugin for the MNG file format. This section moves on to actually getting real data from the file, and inserting it into relevant groups and items.

Adding another item or group

Before we start on a real file, it might be worth reviewing how we actually add additional items and additional groups to a KFile plugin.

Adding an Item

As in the trivial example, we added individual items to the "mngInfo" group with the addItemInfo function in the class constructor, and the addInfo function in the readInfo method.

As an example, lets look at adding another integer item (reprenting a frame rate) and some text to the trivial example from the previous section.

Example 5. source file example, two extra items

	         1	/***************************************************************************
     2	 *   Copyright (C) 2004 by Brad Hards                                      *
     3	 *   bhards@bigpond.net.au                                                 *
     4	 *                                                                         *
     5	 *   This program is free software; you can redistribute it and/or modify  *
     6	 *   it under the terms of the GNU General Public License as published by  *
     7	 *   the Free Software Foundation; either version 2 of the License, or     *
     8	 *   (at your option) any later version.                                   *
     9	 *                                                                         *
    10	 *   This program is distributed in the hope that it will be useful,       *
    11	 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
    12	 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
    13	 *   GNU General Public License for more details.                          *
    14	 *                                                                         *
    15	 *   You should have received a copy of the GNU General Public License     *
    16	 *   along with this program; if not, write to the                         *
    17	 *   Free Software Foundation, Inc.,                                       *
    18	 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
    19	 ***************************************************************************/
       

    20	#include <config.h>
    21	#include "kfile_mng.h"
       

    22	#include <kgenericfactory.h>
       

    23	typedef KGenericFactory<mngPlugin> mngFactory;
       

    24	K_EXPORT_COMPONENT_FACTORY(kfile_mng, mngFactory( "kfile_mng" ))
       

    25	mngPlugin::mngPlugin(QObject *parent, const char *name,
    26	                       const QStringList &args)
    27	    : KFilePlugin(parent, name, args)
    28	{
    29	    KFileMimeTypeInfo* info = addMimeTypeInfo( "video/x-mng" );
       

    30	    // our new group
    31	    KFileMimeTypeInfo::GroupInfo* group = 0L;
    32	    group = addGroupInfo(info, "mngInfo", i18n("MNG Information"));
       

    33	    KFileMimeTypeInfo::ItemInfo* item;
       

    34	    // our new items in the group
    35	    item = addItemInfo(group, "Items", i18n("Items"), QVariant::Int);
    36	    item = addItemInfo(group, "Size", i18n("Size"), QVariant::Int);
    37	    setUnit(item, KFileMimeTypeInfo::KiloBytes);
       

    38	    addItemInfo(group, "framerate", i18n("Frame Rate"), QVariant::Int);
    39	    addItemInfo(group, "Text", i18n("Document type"), QVariant::String);
    40	}
       

    41	bool mngPlugin::readInfo( KFileMetaInfo& info, uint /*what*/)
    42	{
    43	    KFileMetaInfoGroup group = appendGroup(info, "mngInfo");
       

    44	    appendItem(group, "Items", 100);
    45	    appendItem(group, "Size", int(5000/1024));
       

    46	    appendItem(group, "framerate", 40);
    47	    appendItem(group, "Text", "Anonymous MNG file");
    48	    
    49	    return true;
    50	}
       

    51	#include "kfile_mng.moc"
       

	  

All that has changed is the additional addItemInfo function calls in lines 38 and 39, and the additional appendItem calls in lines 46 and 47. Note that in the calls to addItemInfo, we ignore the return value - we don't need a reference to add any units or the like, so it isn't important.

When built and installed, this updated KFile plugin produces meta-data as shown below:

Figure 4. Trivial meta-data properties for MNG image file, with extra items

Note that the framerate label and Text label are never displayed (although Frame Rate and Document Type labels are displayed). The second argument to addItemInfo and appendItem is only used to associate the text label (provided in the third argument to addItemInfo) and the value for that label (provided in the third argument to appendItem.

Adding a group

Adding additional groups is much like adding additional items, except the relevant function calls are addGroupInfo in the constructor, and appendGroup in the readInfo method.

In this example, we'll take the items in our plugin, and divide them into various groups.

Example 6. source file example, with extra groups and items

	         1	/***************************************************************************
     2	 *   Copyright (C) 2004 by Brad Hards                                      *
     3	 *   bhards@bigpond.net.au                                                 *
     4	 *                                                                         *
     5	 *   This program is free software; you can redistribute it and/or modify  *
     6	 *   it under the terms of the GNU General Public License as published by  *
     7	 *   the Free Software Foundation; either version 2 of the License, or     *
     8	 *   (at your option) any later version.                                   *
     9	 *                                                                         *
    10	 *   This program is distributed in the hope that it will be useful,       *
    11	 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
    12	 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
    13	 *   GNU General Public License for more details.                          *
    14	 *                                                                         *
    15	 *   You should have received a copy of the GNU General Public License     *
    16	 *   along with this program; if not, write to the                         *
    17	 *   Free Software Foundation, Inc.,                                       *
    18	 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
    19	 ***************************************************************************/
       

    20	#include <config.h>
    21	#include "kfile_mng.h"
       

    22	#include <kgenericfactory.h>
       

    23	typedef KGenericFactory<mngPlugin> mngFactory;
       

    24	K_EXPORT_COMPONENT_FACTORY(kfile_mng, mngFactory( "kfile_mng" ))
       

    25	mngPlugin::mngPlugin(QObject *parent, const char *name,
    26	                       const QStringList &args)
    27	    : KFilePlugin(parent, name, args)
    28	{
    29	    KFileMimeTypeInfo* info = addMimeTypeInfo( "video/x-mng" );
       

    30	    // our three groups
    31	    KFileMimeTypeInfo::GroupInfo* titleGroup = 0L;
    32	    titleGroup = addGroupInfo(info, "mngTitle", i18n("MNG Title"));
    33	    KFileMimeTypeInfo::GroupInfo* featuresGroup = 0L;
    34	    featuresGroup = addGroupInfo(info, "mngFeatures", i18n("MNG Features"));
    35	    KFileMimeTypeInfo::GroupInfo* sizeGroup = 0L;
    36	    sizeGroup = addGroupInfo(info, "mngSize", i18n("MNG Size"));
       

    37	    KFileMimeTypeInfo::ItemInfo* item;
       

    38	    // our new items in the group
    39	    addItemInfo(featuresGroup, "Items", i18n("Items"), QVariant::Int);
    40	    item = addItemInfo(sizeGroup, "Size", i18n("Size"), QVariant::Int);
    41	    setUnit(item, KFileMimeTypeInfo::KiloBytes);
       

    42	    addItemInfo(featuresGroup, "framerate", i18n("Frame Rate"), QVariant::Int);
    43	    addItemInfo(titleGroup, "Text", i18n("Document type"), QVariant::String);
    44	    addItemInfo(featuresGroup, "Text", i18n("Text lines included"), QVariant::Int);
    45	}
       

    46	bool mngPlugin::readInfo( KFileMetaInfo& info, uint /*what*/)
    47	{
    48	    KFileMetaInfoGroup featuresGroup = appendGroup(info, "mngFeatures");
    49	    KFileMetaInfoGroup titleGroup = appendGroup(info, "mngTitle");
    50	    KFileMetaInfoGroup sizeGroup = appendGroup(info, "mngSize");
       

    51	    appendItem(featuresGroup, "Items", 100);
    52	    appendItem(sizeGroup, "Size", int(5000/1024));
    53	    appendItem(featuresGroup, "Text", 3);
    54	    appendItem(featuresGroup, "framerate", 40);
    55	    appendItem(titleGroup, "Text", "Anonymous MNG file");
    56	    
    57	    return true;
    58	}
       

    59	#include "kfile_mng.moc"
       

	  

In lines 31 to 36, we create the three groups using addGroupInfo. In line 39, we add an item to the features group, while lines 40 and 41 add an item to the size group. Lines 42 and 44 add more items to the features group, while line 43 adds an item to the title group.

Lines 48 to 50 add the various labels to the display, while lines 51 to 55 add the various items to their groups.

There are a couple of things to bring out from this. Note that the items can be added to groups in any order, and groups can be created in any order (although you do have to create the group before you create the item). Also, the same label can be the same for several items, as long as they are assigned to different groups.

When built and installed, this updated KFile plugin produces meta-data as shown below:

Figure 5. Trivial meta-data properties for MNG image file, with extra groups and items

Getting the meta-data out

Now that we understand how to add items and groups, we can move on to using real data instead of the fixed numbers used in the trivial examples.

There are two common approaches for getting the meta-data from a file, irrespective of the actual data that the file contains. The first is to use some form of library (with C or C++ bindings) and obtain the data by function calls. The second approach is to parse the library with standard C++ I/O functions. Each approach has its place, so I'll actually do both, pointing out the advantages of each approach.

Using libmng

The advantage to using a helper library (in this case, libmng) is that it provides an abstraction from the raw file format, which usually makes for easier programming, especially on more complex file formats, or those with numerous variations and assorted bugs.

Using a helper library often also means that when the file format changes, the library interface can remain the same, and the library authors deal with the code changes necessary to handle the underlying format change - your plugin may only need to be relinked to handle the changes. With luck, you may be able to just upgrade the underlying shared libraries and not need to change your plugin at all.

Instead of first trying to get libmng working inside the plugin, I adapted one of the examples provided with libmng to do a dump of the meta-data. When I was confident that I had the rough concept worked out, I moved onto integration of the libmng support into the plugin.

The dependency on libmng means that we should only try to build the plugin if libmng is available, and works. The standard technique for handling this case is an autoconf check. In the integrated build case (that is, part of KDE), we add a file configure.in.in in a convenient place (typically the directory containing the rest of the KFile plugin source), and use the results of the configure check in the next highest level directory.

The configuration check that I came up with looks like the following.

Example 7. configuration check example

	         1	AC_DEFUN([AC_FIND_MNG],
     2	[
     3	AC_REQUIRE([KDE_CHECK_EXTRA_LIBS])
     4	AC_REQUIRE([AC_FIND_ZLIB])
     5	AC_MSG_CHECKING([for libmng])
     6	AC_CACHE_VAL(ac_cv_lib_mng,
     7	[
     8	kde_save_LIBS="$LIBS"
     9	if test "x$kde_use_qt_emb" != "xyes" && test "x$kde_use_qt_mac" != "xyes"; then
    10	LIBS="$LIBS $all_libraries $USER_LDFLAGS -lmng $LIBZ -lm -lX11 $LIBSOCKET"
    11	else
    12	LIBS="$LIBS $all_libraries $USER_LDFLAGS -lmng $LIBZ -lm"
    13	fi
    14	kde_save_CFLAGS="$CFLAGS"
    15	CFLAGS="$CFLAGS $all_includes $USER_INCLUDES"
       

    16	AC_TRY_LINK(dnl
    17	    [
    18	    #include<libmng.h>
    19	    ],
    20	    [
    21	    mng_version_text();
    22	    ],
    23	    eval "ac_cv_lib_mng='-lmng $LIBZ -lm'",
    24	    eval "ac_cv_lib_mng=no"
    25	)
    26	LIBS="$kde_save_LIBS"
    27	CFLAGS="$kde_save_CFLAGS"
    28	])dnl
    29	if eval "test ! \"`echo $ac_cv_lib_mng`\" = no"; then
    30	  AC_DEFINE_UNQUOTED(HAVE_LIBMNG, 1, [Define if you have libmng])
    31	  LIB_MNG="$ac_cv_lib_mng"
    32	  AC_SUBST(LIB_MNG)
    33	  AC_MSG_RESULT($ac_cv_lib_mng)
    34	else
    35	  AC_MSG_RESULT(no)
    36	  LIB_MNG=""
    37	  AC_SUBST(LIB_MNG)
    38	fi
    39	])
       

    40	AC_FIND_MNG
    41	AM_CONDITIONAL(include_MNG_MODULES, test -n "$LIB_MNG")
       

	  

If you haven't seen configure checks like this before, then it might be worth reviewing the manual and info pages for autoconf. For now, it is enough to know that they are written in a macro substitution language known as M4.

I'll work through this quickly. Lines 1 through 39 define the check we will use. Lines 3 and 4 establish some prequisites which will be run if they haven't already been done. Line 5 just outputs a progress message for when ./configure is run. Lines 6 to 28 are the main check, and for potential future efficiency, we cache the results. Lines 8 to 13 establish a suitable set of libraries to test, while line 26 restores the previous value. Lines 14 and 15 establish a suitable set of header file locations, and again we are restoring the previous value at line 27. Lines 16 to 25 tests if a simple file relying on libmng will compile and link. Lines 29 to 38 substitute some appropriate variables depending on whether the compile and link test passed. Line 40 actually invokes the test we just defined, and line 41 sets up an appropriate substitution for automake depending on the results of the test.

As mentioned above, we handle the presence or absence of support libraries by deciding whether to compile a particular directory or not. For example, for code that is in kdegraphics/kfile-plugins/mng/, we use the results of the ./configure check in kdegraphics/kfile-plugins/Makefile.am to determine whether to compile any code in the mng directory. There are other ways to handle it, but this way is common for KFile plugins across KDE, and handles most situations.

The actual change to kdegraphics/kfile-plugins/Makefile.am can be seen in the following file.

Example 8. configuration check Makefile.am example

	         1	if include_EXR_MODULES
     2	KFILE_EXR_SUBDIR=exr
     3	endif
       

     4	if include_TIFF
     5	KFILE_TIFF_SUBDIR=tiff
     6	endif
       

     7	if include_MNG_MODULES
     8	KFILE_MNG_SUBDIR=mng
     9	endif
       

    10	SUBDIRS=dvi pdf png ps jpeg xbm bmp tga ico pcx $(KFILE_TIFF_SUBDIR) pnm \
    11		$(KFILE_EXR_SUBDIR) $(KFILE_MNG_SUBDIR)
	  

In this example file, I've added lines 7 to 9, and the matching entry on the end of the last line. Basically if the configure check returns an appropriate value, then we add the mng directory to the list of subdirectories to compile.

So now we have an appropriate development environment, we can move onto the actual use of libmng. The main code now looks like the following:

Example 9. source file example, using libmng

	         1	/***************************************************************************
     2	 *   Copyright (C) 2004 by Brad Hards                                      *
     3	 *   bhards@bigpond.net.au                                                 *
     4	 *                                                                         *
     5	 *   This program is free software; you can redistribute it and/or modify  *
     6	 *   it under the terms of the GNU General Public License as published by  *
     7	 *   the Free Software Foundation; either version 2 of the License, or     *
     8	 *   (at your option) any later version.                                   *
     9	 *                                                                         *
    10	 *   This program is distributed in the hope that it will be useful,       *
    11	 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
    12	 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
    13	 *   GNU General Public License for more details.                          *
    14	 *                                                                         *
    15	 *   You should have received a copy of the GNU General Public License     *
    16	 *   along with this program; if not, write to the                         *
    17	 *   Free Software Foundation, Inc.,                                       *
    18	 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
    19	 ***************************************************************************/
       

    20	#include <config.h>
    21	#include <libmng.h>
    22	#include "kfile_mng.h"
       

    23	#include <kgenericfactory.h>
       

    24	typedef KGenericFactory<mngPlugin> mngFactory;
       

    25	K_EXPORT_COMPONENT_FACTORY(kfile_mng, mngFactory( "kfile_mng" ))
       

    26	mngPlugin::mngPlugin(QObject *parent, const char *name,
    27	                       const QStringList &args)
    28	    : KFilePlugin(parent, name, args)
    29	{
    30	    KFileMimeTypeInfo* info = addMimeTypeInfo( "video/x-mng" );
       

    31	    KFileMimeTypeInfo::GroupInfo* technicalGroup = 0L;
    32	    technicalGroup = addGroupInfo(info, "mngTechnical", i18n("Technical"));
       

    33	    KFileMimeTypeInfo::ItemInfo* item;
       

    34	    // our new items in the group
    35	    addItemInfo(technicalGroup, "ImageType", i18n("Image Type"), QVariant::Int);
    36	    addItemInfo(technicalGroup, "ImageWidth", i18n("Image Width"), QVariant::Int);
    37	    addItemInfo(technicalGroup, "ImageHeight", i18n("Image Height"), QVariant::Int);
    38	    addItemInfo(technicalGroup, "Ticks", i18n("Ticks"), QVariant::Int);
    39	    addItemInfo(technicalGroup, "Frame Count", i18n("Frame Count"), QVariant::Int);
    40	    addItemInfo(technicalGroup, "Layer Count", i18n("Layer Count"), QVariant::Int);
    41	    item = addItemInfo(technicalGroup, "PlayTime", i18n("Play Time"), QVariant::Int);
    42	    setUnit(item, KFileMimeTypeInfo::Seconds);
    43	    addItemInfo(technicalGroup, "Simplicity", i18n("Simplicity"), QVariant::Int);
    44	}
       

    45	typedef struct user_struct {
    46	          FILE *hFile;                 /* file handle */
    47	        } userdata;
    48	typedef userdata * userdatap;
       

    49	mng_ptr myalloc (mng_size_t iSize)
    50	{
    51	    return (mng_ptr)calloc (1, (size_t)iSize);
    52	}
       

    53	void myfree (mng_ptr pPtr, mng_size_t /*iSize*/)
    54	{
    55	    free (pPtr);
    56	}
       

    57	mng_bool myopenstream (mng_handle /*hMNG*/)
    58	{
    59	  return MNG_TRUE;
    60	}
       

    61	mng_bool myclosestream (mng_handle /*hMNG*/)
    62	{
    63	  return MNG_TRUE;
    64	}
       

    65	mng_bool myreaddata (mng_handle hMNG,
    66	                     mng_ptr    pBuf,
    67	                     mng_uint32 iSize,
    68	                     mng_uint32 *iRead)
    69	{
    70	    userdatap pMydata = (userdatap)mng_get_userdata (hMNG);
    71	    *iRead = fread (pBuf, 1, iSize, pMydata->hFile);
    72	    return MNG_TRUE;
    73	}
       

    74	bool mngPlugin::readInfo( KFileMetaInfo& info, uint /*what*/)
    75	{
    76	    userdatap pMydata;
    77	    mng_handle hMNG;
    78	    mng_retcode iRC;
       

    79	    pMydata = (userdatap)calloc (1, sizeof (userdata));
       

    80	    if (pMydata == NULL) {
    81	        kdWarning() << "Cannot allocate a data buffer" << endl;
    82		return false;
    83	    }
    84	                                       /* can we open the file ? */
    85	    if ((pMydata->hFile = fopen (info.path().ascii(), "rb")) == NULL) {
    86	                                    /* error out if we can't */
    87	        kdWarning() << "Cannot open input file: " << info.path().ascii() << endl;
    88		return false;
    89	    }
    90	                                       /* let's initialize the library */
    91	    hMNG = mng_initialize ((mng_ptr)pMydata, myalloc, myfree, MNG_NULL);
       

    92	    if (!hMNG) {
    93	        kdWarning() << "Cannot initialize libmng" << endl;
    94		return false;
    95	    } else {                                    /* setup callbacks */
    96	        if ( ((iRC = mng_setcb_openstream  (hMNG, myopenstream )) != 0) ||
    97		     ((iRC = mng_setcb_closestream (hMNG, myclosestream)) != 0) ||
    98		     ((iRC = mng_setcb_readdata    (hMNG, myreaddata   )) != 0)    )
    99		    kdWarning() << "Cannot set callbacks for libmng" << endl;
   100		else {                                  /* read the file into memory */
   101		    if ((iRC = mng_read (hMNG)) != 0)
   102		        kdWarning() << "Cannot read the file" << endl;
   103		    else {
       

   104		        KFileMetaInfoGroup technicalGroup = appendGroup(info, "mngTechnical");
       

   105			appendItem(technicalGroup, "ImageType", mng_get_imagetype(hMNG));
   106			appendItem(technicalGroup, "ImageWidth", mng_get_imagewidth(hMNG));
   107			appendItem(technicalGroup, "ImageHeight", mng_get_imageheight(hMNG));
   108			appendItem(technicalGroup, "Ticks", mng_get_ticks(hMNG));
   109			appendItem(technicalGroup, "Frame Count", mng_get_framecount(hMNG));
   110			appendItem(technicalGroup, "Layer Count", mng_get_layercount(hMNG));
   111			appendItem(technicalGroup, "Play time", mng_get_playtime(hMNG));
   112			appendItem(technicalGroup, "Simplicity", mng_get_simplicity(hMNG));
   113		    }
   114		}
       

   115		mng_cleanup (&hMNG);               /* cleanup the library */
   116	    }
       

   117	    fclose (pMydata->hFile);             /* cleanup */
   118	    free (pMydata);
       

   119	    return true;
   120	}
       

   121	#include "kfile_mng.moc"
       

	  

Stepping through the changes since the previous version, we can see that the items have changed (lines 34 to 43) to match what libmng can provide. Lines 45 to 73 establish callback functions, required for use of libmng. Lines 76 to 83 set up some variables and structures. Lines 85 to 89 handle the opening of the file, which is passed in as part of the KFileMetaInfo argument. Line 91 sets up the MNG library, and lines 92 to 94 handle the failure of that setup. Lines 96 to 98 set the callback functions, and line 99 handles any failures in setting the callbacks. Line 101 reads in the header. Lines 105 to 112 use various libmng functions to actually add the entries.

So now we've seen how to make a plugin that can actually do something. Using this code with Konqueror produces a result like the screenshot below.

Figure 6. libmngmeta-data properties for MNG image file

Parsing the file by hand

Instead of using libmng, we could write code to read the values from the file directly. The advantage to parsing the file directly is that it provides finer grained control over the parsing, and avoids a dependency on any external libraries. It can also be used to avoid having to call out to a C-style API, with consequential risks for string handling bugs and the like. Direct parsing is also obviously applicable where the available libraries do not provide a convenient function, or where no libraries are available.

Since there is no need for libmng, we don't need any checks in configure.in.in and the file doesn't need to exist. The build is now also not conditional, so we just add the directory name to the next higher level directory.

TO BE COMPLETED.

Item Units

In previous examples, we've seen items added that have particular semantics associated - for example, the size item in the trivial example had a unit of kilobytes associated with it. There are is a range of particular additional information that can be associated with an item. The main type of information is the units that are associated with an item, although there is also the concept of hints that are used by certain applications.

The available units are:

Second

This item represents a time in seconds

Milliseconds

This item represents a time in thousandths of a second

BitsPerSecond

This item represents a bit rate in bits per second

Pixels

This item represents a number of pixels

Pixels

This item represents a size in pixels

Inches

This item represents a size in inches

Centimeters

This item represents a size in centimetres

Millimeters

This item represents a size in millimetres

Bytes

This item represents a size in bytes

KiloBytes

This item represents a size in kilobytes (1024 bytes)

FramesPerSecond

This item represents a rate in frames per second

Hertz

This item represents a rate in Hertz (1/seconds)

DotsPerInch

This item represents a resolution in dots per inch

BitsPerPixel

This item represents a pixel depth in bits per pixel

The available hints are:

Name

This item is the name or title of the document

Author

This item is the name of the person or organisation that created the content of the file

Description

This item is a text description of the content of the file

Width

This item is a width in pixels. This only has meaning for graphics.

Height

This item is a height in pixels. This only has meaning for graphics.

Size

This item is a width in pixels and a height in pixels. This only has meaning for graphics.

Bitrate

This item is the bitrate for the content. This only has meaning for media files.

Hidden

This item should not normally be shown to the user.

Thumbnail

This item is a thumbnail image of the content of the file. This only has meaning for graphics files.