hi-7

リニアメニューとパイメニューを作ってみましたよ。*1
作ってみて思ったのだけど、パイメニューは色々と面倒ですね。リニアメニューだと、項目数とか項目名の長さとかは、あまり気にしなくてもいいけど、パイメニューだと、項目数が多いと細かく分かれて選びにくいし、項目名が長いと入りきらなかったり、無理して入れようとするとメニューがすごく大きくなってしまうし。
決まり切った項目ならいいのかな。


オブジェクト指向って何だっけ。

#include <iostream>
using namespace std;

#include <X11/Xlib.h>
#include <X11/X.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <locale.h>

#define die { fprintf( stderr, "die at line %d.\n", __LINE__ ); exit(EXIT_FAILURE); }
#define PI 3.1415
#define WIDTH  200
#define HEIGHT 200
#define FONTNAME "*-fixed-medium-r-normal--24-*"
#define LINE_WIDTH 3
#define R 100 //pの半径。

int menu_type = 0;
#define TYPE_L 1
#define TYPE_P 2
/* 1:l, 2:p */
/* 三つ子の魂百まで */
/* というか、一度設定したら変更しない。 */

unsigned long get_color(Display *d, Colormap cmap, char *color_name) {
  XColor exact_color, screen_color;
  XAllocNamedColor(d, cmap, color_name, &screen_color, &exact_color);
  return screen_color.pixel;
}

struct triangle{
  int x1, y1;
  int x2, y2;
  int x3, y3;
};
int position( int x1, int y1, int x2, int y2, int x, int y ){
  /* (x1,y1)と(x2,y2)を通る直線の、上か下か、または(x1==x2)右か左か。 */
  if( x1 == x2 ) return ( x1 < x )? 2 : 3;
  double tan = ( y2 - y1 ) / (double)( x2 - x1 );
  return ( y - y1 < tan * ( x - x1 ) )? 0 : 1;
}
bool on_triangle( struct triangle tri, int x, int y ){
  if( ( position( tri.x1, tri.y1, tri.x2, tri.y2, tri.x3, tri.y3 )
	== position( tri.x1, tri.y1, tri.x2, tri.y2, x, y ) ) &&
      ( position( tri.x2, tri.y2, tri.x3, tri.y3, tri.x1, tri.y1 )
	== position( tri.x2, tri.y2, tri.x3, tri.y3, x, y ) ) &&
      ( position( tri.x3, tri.y3, tri.x1, tri.y1, tri.x2, tri.y2 )
	== position( tri.x3, tri.y3, tri.x1, tri.y1, x, y ) ) ){
    return true;
  }else{
    return false;
  }
}

struct iroiro{
  Display *d;
  Window window;
  GC gc;
  XOC xoc;
  XRectangle logical;
  GC over_back_gc, over_fore_gc;
};
struct label{
  wchar_t * text;
  int length;
  int x, y; //メニューの中心からの相対位置。
  int width, height; //p
};
struct menudata{
  struct label * labels;
  struct triangle * triangles; //p メニューの中心からの相対位置。
  int n;
  int x, y; // メニューの中心。
  int width, height; //l メニュー全体の
};
struct menudata make_menudata( struct iroiro iroiro, struct label * labels ){
  struct menudata menudata;
  XRectangle logical;
  int i, n;
  int height;

  menudata.labels = labels;

  if( menu_type == TYPE_L ){
    menudata.width = 0;
    menudata.height = 0;
  }//TYPE_L
  for( i=0; labels[i].length!=0; i++ ){
    XwcTextExtents( iroiro.xoc, labels[i].text, labels[i].length, NULL, &logical );
    menudata.labels[i].x = - logical.x; // 仮。
    menudata.labels[i].y = - logical.y; // 仮。
    if( menu_type == TYPE_L ){
      if( menudata.width  < logical.width  ) menudata.width  = logical.width;
      if( menudata.height < logical.height ) menudata.height = logical.height;
    }else if( menu_type == TYPE_P ){
      menudata.labels[i].width = logical.width;
      menudata.labels[i].height = logical.height;
    }//TYPE
  }
  n = i;
  menudata.n = n;

  if( menu_type == TYPE_L ){
    for( i=0; i<n; i++ ){
      menudata.labels[i].x += LINE_WIDTH;
      menudata.labels[i].y += LINE_WIDTH + menudata.height * i;
    }
  }//TYPE_L

  if( menu_type == TYPE_P ){
    menudata.triangles = new struct triangle[n];
    double prad = 2 * PI / n; // 1個の角度。
    menudata.triangles[n-1].x1 = menudata.triangles[n-1].y1 = 0;
    menudata.triangles[n-1].x3 = menudata.triangles[0].x2
      = (int)( R * sin( - prad / 2 ) );
    menudata.triangles[n-1].y3 = menudata.triangles[0].y2
      = (int)( - R * cos( - prad / 2 ) );
    for( i=0; i<n-1; i++ ){
      menudata.triangles[i].x1 = menudata.triangles[i].y1 = 0;
      menudata.triangles[i].x3 = menudata.triangles[i+1].x2
	= (int)( R * sin( prad * ( i + 0.5 ) ) );
      menudata.triangles[i].y3 = menudata.triangles[i+1].y2
	= (int)( - R * cos( prad * ( i + 0.5 ) ) );
    }
    for( i=0; i<n; i++ ){
      menudata.labels[i].x += ( 0 + menudata.triangles[i].x2 + menudata.triangles[i].x3 ) / 3
	- menudata.labels[i].width / 2;
      menudata.labels[i].y += ( 0 + menudata.triangles[i].y2 + menudata.triangles[i].y3 ) / 3
	- menudata.labels[i].height / 2;
    }
  }//TYPE_P

  return menudata;
}
  
void menu( struct iroiro iroiro, struct menudata menudata ){
  struct label * labels = menudata.labels;
  int x = menudata.x;
  int y = menudata.y;
  int n = menudata.n;

  XClearWindow( iroiro.d, iroiro.window );

  if( menu_type == TYPE_L ){
    struct label* labels = menudata.labels;
    XDrawRectangle( iroiro.d, iroiro.window, iroiro.gc, x, y,
		    menudata.width + LINE_WIDTH * 2, menudata.height * n + LINE_WIDTH * 2 );
  }else if( menu_type == TYPE_P ){
    XPoint p[3];
    for( int i=0; i<n; i++ ){
      p[0].x = x + menudata.triangles[i].x1;
      p[0].y = y + menudata.triangles[i].y1;
      p[1].x = x + menudata.triangles[i].x2;
      p[1].y = y + menudata.triangles[i].y2;
      p[2].x = x + menudata.triangles[i].x3;
      p[2].y = y + menudata.triangles[i].y3;
      XDrawLines( iroiro.d, iroiro.window, iroiro.gc, p, 3, CoordModeOrigin );
    }
  }
  for( int i=0; i<n; i++ ){
    XwcDrawString( iroiro.d, iroiro.window, iroiro.xoc, iroiro.gc,
		   x + labels[i].x, y + labels[i].y,
		   labels[i].text, labels[i].length );
  }
}
int get_index( struct menudata menudata, int x, int y ){
  if( menu_type == TYPE_L ){
    if( ( x < menudata.x + LINE_WIDTH ) ||
	( menudata.x + LINE_WIDTH + menudata.width < x ) ||
	( y < menudata.y + LINE_WIDTH ) ||
	( menudata.y + LINE_WIDTH + menudata.height * menudata.n < y ) ){
      return -1;
    }
    for( int i=0; i<menudata.n; i++ ){
      if( y < menudata.y + LINE_WIDTH + menudata.height * (i+1) ){
	return i;
      }
    }
  }else if( menu_type == TYPE_P ){
    if( ( menudata.x - LINE_WIDTH < x ) && ( x < menudata.x + LINE_WIDTH ) &&
	( menudata.y - LINE_WIDTH < y ) && ( y < menudata.y + LINE_WIDTH ) ){
      return -1;
    }
    for( int i=0; i<menudata.n; i++ ){
      if( on_triangle( menudata.triangles[i], x - menudata.x, y - menudata.y ) ) return i;
    }
  }//TYPE

  return -1;
}
void over_menu( struct iroiro iroiro, struct menudata menudata, int index ){
  menu( iroiro, menudata );

  if( index < 0 ) return;

  int x = menudata.x;
  int y = menudata.y;
  struct label * labels = menudata.labels;
  XRectangle logical = iroiro.logical;
  int i = index;

  if( menu_type == TYPE_L ){
    XFillRectangle( iroiro.d, iroiro.window, iroiro.over_back_gc,
		    x + LINE_WIDTH, y + menudata.height * i + LINE_WIDTH,
		    menudata.width, menudata.height );
  }else if( menu_type == TYPE_P ){
    XPoint p[3];
    p[0].x = x + menudata.triangles[i].x1;
    p[0].y = y + menudata.triangles[i].y1;
    p[1].x = x + menudata.triangles[i].x2;
    p[1].y = y + menudata.triangles[i].y2;
    p[2].x = x + menudata.triangles[i].x3;
    p[2].y = y + menudata.triangles[i].y3;
    XFillPolygon( iroiro.d, iroiro.window, iroiro.over_back_gc, p, 3, Nonconvex, CoordModeOrigin );
  }//TYPE
  XwcDrawString( iroiro.d, iroiro.window, iroiro.xoc, iroiro.over_fore_gc,
		 x + menudata.labels[i].x, y + menudata.labels[i].y,
		 labels[i].text, labels[i].length );
}

int main(int argc, char ** argv){
  Display *d;
  Colormap cmap;
  int screen;
  Window root, window;

  // --- 国際化テキストを扱うための準備。
  if (setlocale(LC_ALL, "")==NULL) {
    cerr << "Can't set locale.\n";
    exit(1);
  }
  if ((d=XOpenDisplay(NULL))==NULL) {
    cerr << "Can't open display.\n";
    exit(1);
  }
  if (XSupportsLocale()==False) {
    cerr << "unsupported locale.\n";
    exit(1);
  }
  if (XSetLocaleModifiers("")==NULL) {
    cerr << "Can't set locale modifiers.\n";
    exit(1);
  }

  // --- Xクライアントの一般的な初期化。
  root = DefaultRootWindow(d);
  screen = DefaultScreen(d);
  cmap = DefaultColormap(d, screen);

  window = XCreateSimpleWindow(d, root, 0, 0, WIDTH, HEIGHT, 3,
			       BlackPixel(d, screen), WhitePixel(d, screen));

  // --- テキスト出力関連の初期化。
  XOM xom = XOpenOM(d, NULL, NULL, NULL);
  XOC xoc = XCreateOC(xom, XNBaseFontName, FONTNAME,
		      NULL);
  if(xoc == NULL){
    cerr << "Can't create OC.\n";
    exit(1);
  }

  XRectangle logical;
  XwcTextExtents(xoc, L"iあ", 2, NULL, &logical);


  XGCValues xgcv;
  xgcv.line_width = LINE_WIDTH;
  GC gc=XCreateGC(d, window, GCLineWidth, &xgcv);
  xgcv.foreground = get_color( d, cmap, "blue" );
  GC over_back_gc = XCreateGC( d, window, GCForeground, &xgcv );
  xgcv.foreground = get_color( d, cmap, "white" );
  GC over_fore_gc = XCreateGC( d, window, GCForeground, &xgcv );

  XSelectInput(d, window, ExposureMask|ButtonPressMask|ButtonReleaseMask|KeyPressMask|PointerMotionMask);
  XMapWindow(d, window);

  struct iroiro iroiro;
  iroiro.d = d;
  iroiro.window = window;
  iroiro.gc = gc;
  iroiro.xoc = xoc;
  iroiro.logical = logical;
  iroiro.over_back_gc = over_back_gc;
  iroiro.over_fore_gc = over_fore_gc;

  if( argc < 2 ) die;
  if( argv[1][1] == 'L' ){
    menu_type = TYPE_L;
  }else if( argv[1][1] == 'P' ){
    menu_type = TYPE_P;
  }else{
    die;
  }

  struct label labels[argc-1];
  for( int i=2; i<argc; i++ ){
    wchar_t * buf;
    int len = mbstowcs( NULL, argv[i], 0 ) + 1;
    buf = new wchar_t[len];
    labels[i-2].length = mbstowcs( buf, argv[i], len );
    labels[i-2].text = buf;
  }
  labels[argc-2].length = 0;

  struct menudata menudata = make_menudata( iroiro, labels );
  int overindex = -1;
  bool open_menu = false;

  XEvent e;
  int index;
  for( bool done=false; done == false; ){
    XNextEvent(d, &e);
    switch( e.type ){
    case KeyPress :
      // 終了する。
      done = true;
      break;
    case ButtonPress :
      if( !open_menu ){
	open_menu = true;
	menudata.x = e.xbutton.x;
	menudata.y = e.xbutton.y;
	menu( iroiro, menudata );
      }else{ // メニューが開いてる時。
	index = get_index( menudata, e.xbutton.x, e.xbutton.y );
	if( index >= 0 ) break; // メニュー外の時。
	open_menu = false;
	XClearWindow( d, window );
      }
      break;
    case ButtonRelease :
      if( !open_menu ) break; // メニューが開いてる時のみ。
      index = get_index( menudata, e.xbutton.x, e.xbutton.y );
      if( index >= 0 ){
	printf( "[%d] = '%s'\n", index, argv[index+2] );
	open_menu = false;
	XClearWindow( d, window );
      }
      break;
    case MotionNotify :
      if( !open_menu ) break; // メニューが開いてる時のみ。
      index = get_index( menudata, e.xmotion.x, e.xmotion.y );
      if( overindex == index ) break;
      overindex = index;
      over_menu( iroiro, menudata, index );
      break;
    }
  }
  return 0;
}

*1:〆切過ぎてますね。