Index: /trunk/Ohana/src/opihi/cmd.basic/Makefile
===================================================================
--- /trunk/Ohana/src/opihi/cmd.basic/Makefile	(revision 40521)
+++ /trunk/Ohana/src/opihi/cmd.basic/Makefile	(revision 40522)
@@ -54,4 +54,5 @@
 $(SRC)/strlen.$(ARCH).o     \
 $(SRC)/substr.$(ARCH).o     \
+$(SRC)/strstr.$(ARCH).o     \
 $(SRC)/strhash.$(ARCH).o     \
 $(SRC)/strmatch.$(ARCH).o     \
Index: /trunk/Ohana/src/opihi/cmd.basic/init.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.basic/init.c	(revision 40521)
+++ /trunk/Ohana/src/opihi/cmd.basic/init.c	(revision 40522)
@@ -42,4 +42,5 @@
 int strmatch        PROTO((int, char **));
 int substr_func     PROTO((int, char **));
+int strstr_func     PROTO((int, char **));
 int strpop          PROTO((int, char **));
 int strhash         PROTO((int, char **));
@@ -94,5 +95,6 @@
   {1, "fprintf",       fprintf_opihi,      "formatted print to standard output"},
   {1, "strlen",        strlen_func,        "string length"},
-  {1, "substr",        substr_func,        "substring"},
+  {1, "substr",        substr_func,        "extract a substring"},
+  {1, "strstr",        strstr_func,        "find a substring"},
   {1, "strhash",       strhash,            "generate a hash for a string"},
   {1, "strpop",        strpop,             "pop a string"},
Index: /trunk/Ohana/src/opihi/cmd.basic/strstr.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.basic/strstr.c	(revision 40522)
+++ /trunk/Ohana/src/opihi/cmd.basic/strstr.c	(revision 40522)
@@ -0,0 +1,50 @@
+# include "basic.h"
+
+int strstr_func (int argc, char **argv) {
+
+  int N;
+
+  int VERBOSE = TRUE;
+  if ((N = get_argument (argc, argv, "-q"))) {
+    remove_argument (N, &argc, argv);
+    VERBOSE = FALSE;
+  }
+
+  int start = -1;
+  char *startName = NULL;
+  if ((N = get_argument (argc, argv, "-start"))) {
+    remove_argument (N, &argc, argv);
+    startName = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  int length = 0;
+  char *lengthName = NULL;
+  if ((N = get_argument (argc, argv, "-length"))) {
+    remove_argument (N, &argc, argv);
+    lengthName = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  /* returns range of the requested string */ 
+  if (argc != 3) {
+    gprint (GP_ERR, "USAGE: strstr (haystack) (needle) [-start (var)] [-length (var)]\n");
+    gprint (GP_ERR, "  return the starting position and length of needle in haystack\n");
+    return (FALSE);
+  }
+
+  char *c = strstr (argv[1], argv[2]);
+
+  // set the result if we found the needle
+  if (c != NULL) {
+    start = c - argv[1];
+    length = strlen(argv[2]);
+  }
+
+  if (startName) set_variable (startName, start);
+  if (lengthName) set_variable (lengthName, length);
+
+  if (VERBOSE) gprint (GP_ERR, "start: %d, length: %d\n", start, length);
+
+  return (TRUE);
+}
Index: /trunk/Ohana/src/opihi/cmd.basic/substr.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.basic/substr.c	(revision 40521)
+++ /trunk/Ohana/src/opihi/cmd.basic/substr.c	(revision 40522)
@@ -7,5 +7,5 @@
 
   if ((argc != 4) && (argc != 5)) {
-    gprint (GP_ERR, "USAGE: substr (string) N1 N2 [var]\n");
+    gprint (GP_ERR, "USAGE: substr (string) Nstart Nlength [var]\n");
     return (FALSE);
   }
@@ -20,5 +20,5 @@
   }
   if ((N2 < 0) || ((N2+N1) >  strlen(argv[1]))) {
-      gprint (GP_ERR, "ERROR: end value out of range in substr command\n");
+      gprint (GP_ERR, "ERROR: length out of range in substr command\n");
       return (FALSE);
   }
Index: /trunk/Ohana/src/opihi/cmd.data/Makefile
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/Makefile	(revision 40521)
+++ /trunk/Ohana/src/opihi/cmd.data/Makefile	(revision 40522)
@@ -153,4 +153,5 @@
 $(SRC)/textline.$(ARCH).o	   \
 $(SRC)/threshold.$(ARCH).o		\
+$(SRC)/triangle.$(ARCH).o		   \
 $(SRC)/tv.$(ARCH).o		   \
 $(SRC)/tvchannel.$(ARCH).o	   \
Index: /trunk/Ohana/src/opihi/cmd.data/init.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/init.c	(revision 40521)
+++ /trunk/Ohana/src/opihi/cmd.data/init.c	(revision 40522)
@@ -138,4 +138,5 @@
 int textline         PROTO((int, char **));
 int threshold        PROTO((int, char **));
+int triangle         PROTO((int, char **));
 int tv               PROTO((int, char **));
 int tvchannel        PROTO((int, char **));
@@ -323,4 +324,5 @@
   {1, "textline",     textline,         "write text line on graph"},
   {1, "threshold",    threshold,        "find (interpolate) location of transition"},
+  {1, "triangle",     triangle,         "fill a triangular region with a value"},
   {1, "tv",           tv,               "display an image on the Kii window"},
   {1, "tvchannel",    tvchannel,        "set the current tv channel"},
Index: /trunk/Ohana/src/opihi/cmd.data/triangle.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/triangle.c	(revision 40522)
+++ /trunk/Ohana/src/opihi/cmd.data/triangle.c	(revision 40522)
@@ -0,0 +1,108 @@
+# include "data.h"
+
+# ifndef iSWAP
+# define iSWAP(X,Y) {int tmp=(X); (X) = (Y); (Y) = tmp;}
+# endif
+
+int FillLineHorizontal (Buffer *buf, int Xs, int Xe, int Y, float value);
+
+// algorithm adapted from http://www-users.mat.uni.torun.pl/~wrona/3d_tutor/tri_fillers.html
+int triangle (int argc, char **argv) {
+
+  int N;
+  Buffer *buf;
+
+  float value = 0;
+  if ((N = get_argument (argc, argv, "-v"))) {
+    remove_argument (N, &argc, argv);
+    value  = atof(argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  if (argc != 8) {
+    gprint (GP_ERR, "USAGE: triangle <buffer> Xa Ya Xb Yb Xc Yc [-v value]\n");
+    return (FALSE);
+  }
+  if ((buf = SelectBuffer (argv[1], OLDBUFFER, TRUE)) == NULL) return (FALSE);
+
+  float Xa = atof (argv[2]);
+  float Ya = atof (argv[3]);
+  float Xb = atof (argv[4]);
+  float Yb = atof (argv[5]);
+  float Xc = atof (argv[6]);
+  float Yc = atof (argv[7]);
+
+  // XXX perhaps allow points out of bounds and avoid them in the horizline function?
+  // if (Xa < 0) goto error;
+  // if (Xb < 0) goto error;
+  // if (Xc < 0) goto error;
+  // if (Ya < 0) goto error;
+  // if (Yb < 0) goto error;
+  // if (Yc < 0) goto error;
+  // 
+  // if (Xa >= buf[0].matrix.Naxis[0]) goto error;
+  // if (Xb >= buf[0].matrix.Naxis[0]) goto error;
+  // if (Xc >= buf[0].matrix.Naxis[0]) goto error;
+  // if (Ya >= buf[0].matrix.Naxis[1]) goto error;
+  // if (Yb >= buf[0].matrix.Naxis[1]) goto error;
+  // if (Yc >= buf[0].matrix.Naxis[1]) goto error;
+
+  // sort the points so Ya <= Yb <= Yc
+  if (Yc < Yb) { SWAP (Xc, Xb); SWAP (Yc, Yb); }
+  if (Yc < Ya) { SWAP (Xc, Xa); SWAP (Yc, Ya); }
+  if (Yb < Ya) { SWAP (Xb, Xa); SWAP (Yb, Ya); }
+
+  float dy1 = Yb - Ya;
+  float dy2 = Yc - Ya;
+  float dy3 = Yc - Yb;
+
+  float dx1 = (dy1 > 0) ? (Xb - Xa) / dy1 : 0;
+  float dx2 = (dy2 > 0) ? (Xc - Xa) / dy2 : 0;
+  float dx3 = (dy3 > 0) ? (Xc - Xb) / dy3 : 0;
+
+  float Xs = Xa, Xe = Xa;
+
+  if (dx1 > dx2) {
+    for (int y = Ya; y <= Yb; y++, Xs += dx2, Xe += dx1) {
+      FillLineHorizontal (buf, (int) Xs, (int) Xe, y, value);
+    }
+    Xe = Xb; // is this needed?
+    for (int y = Yb; y <= Yc; y++, Xs += dx2, Xe += dx3) {
+      FillLineHorizontal (buf, (int) Xs, (int) Xe, y, value);
+    }
+  } else {
+    for (int y = Ya; y <= Yb; y++, Xs += dx1, Xe += dx2) {
+      FillLineHorizontal (buf, (int) Xs, (int) Xe, y, value);
+    }
+    Xs = Xb; // is this needed?
+    for (int y = Yb; y <= Yc; y++, Xs += dx3, Xe += dx2) {
+      FillLineHorizontal (buf, (int) Xs, (int) Xe, y, value);
+    }
+  }
+
+  return (TRUE);
+
+ // error:
+ //  gprint (GP_ERR, "region out of range\n");
+ //  return (FALSE);
+}
+
+// we fill in the pixels from Xs to Xe INCLUSIVE
+int FillLineHorizontal (Buffer *buf, int Xs, int Xe, int Y, float value) {
+
+  if (Y < 0) return FALSE;
+  if (Y >= buf[0].matrix.Naxis[1]) return FALSE;
+
+  int Xstart = MIN (MAX (Xs, 0), buf[0].matrix.Naxis[0] - 1);
+  int Xend   = MIN (MAX (Xe, 0), buf[0].matrix.Naxis[0] - 1);
+
+  if (Xend < Xstart) iSWAP (Xstart, Xend);
+
+  int Yoff = Y * buf[0].matrix.Naxis[0];
+
+  float *V = (float *) buf[0].matrix.buffer;
+  for (int i = Xstart; i <= Xend; i++) {
+    V[i + Yoff] = value;
+  }
+  return TRUE;
+}
