1// @configure_input@
2
3/**************************************************************************\
4 * Copyright (c) Kongsberg Oil & Gas Technologies AS
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are
9 * met:
10 *
11 * Redistributions of source code must retain the above copyright notice,
12 * this list of conditions and the following disclaimer.
13 *
14 * Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * Neither the name of the copyright holder nor the names of its
19 * contributors may be used to endorse or promote products derived from
20 * this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33\**************************************************************************/
34
35#ifdef HAVE_CONFIG_H
36#include <config.h>
37#endif /* HAVE_CONFIG_H */
38
39#include <assert.h>
40#include <string.h>
41#include <sys/types.h>
42#include <sys/stat.h>
43#ifdef HAVE_DIRENT_H
44#include <dirent.h>
45#endif
46#ifdef HAVE_UNISTD_H
47#include <unistd.h>
48#endif
49#include <stdlib.h>
50
51#include <Inventor/SbPList.h>
52#include <Inventor/errors/SoDebugError.h>
53
54#include <so@gui@defs.h>
55#include <Inventor/@Gui@/SoAnyMaterialList.h>
56#include <Inventor/@Gui@/SoAny.h>
57
58struct So@Gui@MaterialListCallbackInfo {
59  So@Gui@MaterialListCB * callback;
60  void * closure;
61};
62
63/*!
64  \class SoAnyMaterialList Inventor/@Gui@/SoAnyMaterialList.h
65  \brief The SoAnyMaterialList class is the common code for the MaterialList
66  component classes.
67*/
68
69// *************************************************************************
70
71/*!
72*/
73
74SoAnyMaterialList::SoAnyMaterialList(
75  const char * const dir)
76{
77  this->callbacks = NULL;
78  this->dirpath = NULL;
79  this->directory = NULL;
80  if (dir != NULL)
81    this->dirpath = strcpy(new char [strlen(dir)+1], dir);
82}
83
84/*!
85*/
86
87SoAnyMaterialList::~SoAnyMaterialList(
88  void)
89{
90  if (this->callbacks != NULL) {
91    const int num = this->callbacks->getLength();
92    for (int i = 0; i < num; i++)
93      delete (So@Gui@MaterialListCallbackInfo *) (*this->callbacks)[i];
94    delete this->callbacks;
95  }
96  if (this->dirpath)
97    delete [] this->dirpath;
98  if (this->directory != NULL)
99    this->freeMaterialDirectory();
100}
101
102// *************************************************************************
103
104/*!
105*/
106
107void
108SoAnyMaterialList::addCallback(
109  So@Gui@MaterialListCB * const callback,
110  void * const closure)
111{
112  if (this->callbacks == NULL)
113    this->callbacks = new SbPList;
114  So@Gui@MaterialListCallbackInfo * info =
115    new So@Gui@MaterialListCallbackInfo;
116  info->callback = callback;
117  info->closure = closure;
118  this->callbacks->append(info);
119} // addCallback()
120
121/*!
122*/
123
124void
125SoAnyMaterialList::removeCallback(
126  So@Gui@MaterialListCB * const callback,
127  void * const closure)
128{
129  if (! this->callbacks) {
130#if SO@GUI@_DEBUG
131    SoDebugError::postInfo("SoAnyMaterialList::removeCallback",
132      "component has zero callbacks set.");
133#endif // SO@GUI@_DEBUG
134    return;
135  }
136
137  const int numcallbacks = this->callbacks->getLength();
138  for (int i = 0; i < numcallbacks; i++) {
139    So@Gui@MaterialListCallbackInfo * info =
140      (So@Gui@MaterialListCallbackInfo *) (*this->callbacks)[i];
141    if (info->callback == callback && info->closure == closure) {
142      this->callbacks->remove(i);
143      delete info;
144      return;
145    }
146  }
147
148#if SO@GUI@_DEBUG
149  SoDebugError::postInfo("SoAnyMaterialList::removeCallback",
150    "callback was not set for component.");
151#endif // SO@GUI@_DEBUG
152} // removeCallback()
153
154/*!
155*/
156
157void
158SoAnyMaterialList::invokeCallbacks(
159  SoMaterial * material)
160{
161  if (this->callbacks) {
162    const int numCallbacks = this->callbacks->getLength();
163    for (int i = 0; i < numCallbacks; i++) {
164      So@Gui@MaterialListCallbackInfo * info =
165        (So@Gui@MaterialListCallbackInfo *) (*this->callbacks)[i];
166      info->callback(info->closure, (SoMaterial *) material);
167    }
168  }
169} // invokeCallbacks()
170
171// *************************************************************************
172
173/*!
174  \internal
175
176  This method frees up the memory used by the material-index data strucure.
177*/
178
179void
180SoAnyMaterialList::freeMaterialDirectory(
181  void)
182{
183  if (this->directory == NULL)
184    return;
185  int i, j;
186  if ((this->directory->flags & SO@GUI@_BUILTIN_MATERIALS) == 0) {
187    // all data is allocated
188    for (i = 0; i < this->directory->numGroups; i++) {
189      for (j = 0; j < this->directory->groups[i]->numMaterials; j++) {
190        delete [] (char *) this->directory->groups[i]->materials[j]->data;
191        delete [] (char *) this->directory->groups[i]->materials[j]->name;
192        delete this->directory->groups[i]->materials[j];
193      }
194      delete [] (char *) this->directory->groups[i]->name;
195      delete [] this->directory->groups[i]->materials;
196      delete this->directory->groups[i];
197    }
198    delete [] this->directory->groups;
199  } else {
200    // lots of data is static and should therefore not be freed
201    for (i = 0; i < this->directory->numGroups; i++) {
202      for (j = 0; j < this->directory->groups[i]->numMaterials; j++)
203        delete this->directory->groups[i]->materials[j];
204      delete [] this->directory->groups[i]->materials;
205      delete this->directory->groups[i];
206    }
207    delete [] this->directory->groups;
208  }
209  delete this->directory;
210  this->directory = NULL;
211} // freeMaterialDirectory()
212
213// *************************************************************************
214
215const char *
216SoAnyMaterialList::getMaterialDirectoryPath(
217  void) const
218{
219  return this->dirpath;
220} // getMaterialDirectoryPath()
221
222// *************************************************************************
223
224/*!
225  \internal
226*/
227
228static
229int
230containsFiles(
231  const char * const path)
232{
233#ifdef HAVE_DIRENT_H
234  DIR * directory = opendir(path);
235  struct dirent * entry;
236  struct stat entrystats;
237  if (directory != NULL) {
238    const int pathnamelen = strlen(path);
239    int foundfile = 0;
240    while (! foundfile && (entry = readdir(directory)) != NULL) {
241      if (entry->d_name[0] == '.') continue;
242      char * entrypath = new char [pathnamelen + strlen(entry->d_name) + 2];
243      sprintf(entrypath, "%s/%s", path, entry->d_name);
244      if (stat(entrypath, &entrystats) == 0 &&
245           S_ISREG(entrystats.st_mode)) {
246        delete [] entrypath;
247        closedir(directory);
248        return 1;
249      }
250      delete [] entrypath;
251    }
252    closedir(directory);
253  }
254#endif
255  return 0;
256} // containsFiles()
257
258/*!
259  \internal
260*/
261
262char **
263SoAnyMaterialList::getNonemptySubdirs(// static, private
264  const char * const path)
265{
266#ifdef HAVE_DIRENT_H
267  DIR * dir = opendir(path);
268  if (! dir) return NULL;
269
270  SbPList subdirs;
271  DIR * subdir;
272  const int pathlen = strlen(path) + 2;
273  struct dirent * entry, * subentry;
274  struct stat statbuf, substatbuf;
275  while ((entry = readdir(dir)) != NULL) {
276    if (entry->d_name[0] == '.') continue;
277    int pathnamelen = pathlen + strlen(entry->d_name);
278    char * pathname = new char [ pathnamelen ];
279    sprintf(pathname, "%s/%s", path, entry->d_name);
280    if ((stat(pathname, &statbuf) == 0) && S_ISDIR(statbuf.st_mode)) {
281      if ((subdir = opendir(pathname)) != NULL) {
282        int foundfile = 0;
283        while (! foundfile && (subentry = readdir(subdir)) != NULL) {
284          if (subentry->d_name[0] == '.') continue;
285          char * entrypathname =
286            new char [pathnamelen + strlen(subentry->d_name) + 1];
287          sprintf(entrypathname, "%s/%s", pathname, subentry->d_name);
288          if (stat(entrypathname, &substatbuf) == 0 &&
289               S_ISREG(substatbuf.st_mode)) {
290            foundfile = 1;
291          }
292          delete [] entrypathname;
293        }
294        if (foundfile)
295          subdirs.append(strcpy(new char [strlen(entry->d_name)+1],
296                                  entry->d_name));
297        closedir(subdir);
298      }
299    }
300    delete [] pathname;
301  }
302  closedir(dir);
303
304  const int num = subdirs.getLength();
305  char ** subdirarray = new char * [ num + 1 ];
306  for (int i = 0; i < num; i++)
307    subdirarray[i] = (char *) subdirs[i];
308  subdirarray[num] = NULL;
309  return subdirarray;
310#endif
311  return NULL;
312} // numNonemptySubdirs()
313
314
315/*!
316  \internal
317*/
318
319char **
320SoAnyMaterialList::getRegularFiles(// static, private
321  const char * const path)
322{
323#ifdef HAVE_DIRENT_H
324  DIR * dir = opendir(path);
325  if (! dir) return NULL;
326
327  const int pathlen = strlen(path);
328  SbPList files;
329  struct dirent * entry;
330  while ((entry = readdir(dir)) != NULL) {
331    if (entry->d_name[0] == '.') continue;
332    char * pathname = new char [pathlen + 2 + strlen(entry->d_name)];
333    sprintf(pathname, "%s/%s", path, entry->d_name);
334    struct stat statbuf;
335    if ((stat(pathname, &statbuf) == 0) && S_ISREG(statbuf.st_mode))
336      files.append(strcpy(new char [strlen(entry->d_name)+1],
337                            entry->d_name));
338    delete [] pathname;
339  }
340  closedir(dir);
341
342  const int num = files.getLength();
343  char ** filearray = new char * [ num + 1 ];
344  for (int i = 0; i < num; i++)
345    filearray[i] = (char *) files[i];
346  filearray[num] = NULL;
347  return filearray;
348#endif
349  return NULL;
350} // getRegularFiles()
351
352/*!
353  \internal
354
355  Used for qsort().
356*/
357
358int
359SoAnyMaterialList::qsort_comparator(// static, private
360  const void * itemA,
361  const void * itemB)
362{
363  SO@GUI@_STUB();
364  return 0;
365} // qsort_comparator()
366
367/*!
368*/
369
370So@Gui@MaterialDirectory *
371SoAnyMaterialList::getMaterialDirectory(
372  void)
373{
374  if (this->directory != NULL) // already created
375    return this->directory;
376
377  // get the path to search for materials in
378  SbString path;
379  if (this->dirpath) { path = this->dirpath; }
380
381  const char * envvars[] = {
382    // FIXME: where the hell does the "WALLET" env-var come from?
383    // 20020109 mortene.
384    "SO_MATERIAL_DIR", "WALLET", "COIN_HOME"
385  };
386
387  for (unsigned int i=0; i < (sizeof(envvars) / sizeof(char *)); i++) {
388    if (path.getLength() == 0) {
389      const char * p = SoAny::si()->getenv(envvars[i]);
390      if (p) { path = p; }
391    }
392  }
393
394  if (path.getLength() > 0) { path += "/materials"; }
395  else {
396    const char * p = SoAny::si()->getenv("OIVHOME");
397    if (p) {
398      path = p;
399      // FIXME: use DIRSEP string? ????-??-?? larsa.
400      path += "/data/materials";
401    }
402  }
403
404  this->directory = new So@Gui@MaterialDirectory;
405  this->directory->flags = 0;
406  this->directory->current = 0;
407
408  SbBool founddiskmaterials = FALSE;
409
410  if (path.getLength() > 0) {
411#if SO@GUI@_DEBUG && 0 // debug
412    SoDebugError::postInfo("SoAnyMaterialList::getMaterialDirectory",
413                           "scanning '%s'", path.getString());
414#endif // debug
415    char ** subdirs = SoAnyMaterialList::getNonemptySubdirs(path.getString());
416    if (subdirs) {
417      founddiskmaterials = TRUE;
418      int numsubdirs;
419      for (numsubdirs = 0; subdirs[numsubdirs] != NULL; numsubdirs++) { }
420      this->directory->numGroups = numsubdirs;
421      this->directory->groups = new So@Gui@MaterialGroup * [ numsubdirs ];
422      for (int subdir = 0; subdir < numsubdirs; subdir++) {
423        this->directory->groups[subdir] = new So@Gui@MaterialGroup;
424        this->directory->groups[subdir]->name = subdirs[subdir];
425        char * subdirname =
426          new char [strlen(path.getString()) + strlen(subdirs[subdir]) + 2];
427        sprintf(subdirname, "%s/%s", path.getString(), subdirs[subdir]);
428        char ** files = SoAnyMaterialList::getRegularFiles(subdirname);
429        assert(files != NULL);
430        int numfiles;
431        for (numfiles = 0; files[numfiles] != NULL; numfiles++) { }
432        this->directory->groups[subdir]->numMaterials = numfiles;
433        this->directory->groups[subdir]->materials =
434          new So@Gui@Material * [ numfiles ];
435        for (int file = 0; file < numfiles; file++) {
436          this->directory->groups[subdir]->materials[file] =
437            new So@Gui@Material;
438          this->directory->groups[subdir]->materials[file]->name =
439            files[file];
440          char * buf = new char [strlen(subdirname) + strlen(files[file]) + 2];
441          sprintf(buf, "%s/%s", subdirname, files[file]);
442          this->directory->groups[subdir]->materials[file]->data = buf;
443        }
444        delete [] files; // actual strings are transfered, don't delete them!
445        delete [] subdirname;
446      }
447      delete [] subdirs; // actual strings are transfered, don't delete them!
448    }
449  }
450
451  // fallback on builtins
452  if (!founddiskmaterials) { this->setupBuiltinMaterials(this->directory); }
453  return this->directory;
454} // getMaterialDirectory()
455
456// *************************************************************************
457
458