curvedraw

annotate src/app.cc @ 10:95fada20c638

foo
author John Tsiombikas <nuclear@member.fsf.org>
date Sat, 19 Dec 2015 17:40:27 +0200
parents 6e980fddbf3b
children 84a647283237
rev   line source
nuclear@0 1 #include <stdlib.h>
nuclear@0 2 #include <float.h>
nuclear@2 3 #include <assert.h>
nuclear@0 4 #include <vector>
nuclear@0 5 #include <algorithm>
nuclear@0 6 #include "opengl.h"
nuclear@0 7 #include "app.h"
nuclear@0 8 #include "curve.h"
nuclear@0 9 #include "widgets.h"
nuclear@6 10 #include "curvefile.h"
nuclear@0 11
nuclear@3 12 enum SnapMode {
nuclear@3 13 SNAP_NONE,
nuclear@3 14 SNAP_GRID,
nuclear@3 15 SNAP_POINT
nuclear@3 16 };
nuclear@3 17
nuclear@1 18 int win_width, win_height;
nuclear@1 19 float win_aspect;
nuclear@1 20
nuclear@0 21 static void draw_grid(float sz, float sep, float alpha = 1.0f);
nuclear@0 22 static void draw_curve(const Curve *curve);
nuclear@0 23 static void on_click(int bn, float u, float v);
nuclear@0 24
nuclear@2 25 // viewport control
nuclear@2 26 static Vector2 view_pan;
nuclear@3 27 static float view_scale = 0.2f;
nuclear@3 28 static Matrix4x4 view_matrix;
nuclear@3 29
nuclear@3 30 static float grid_size = 1.0;
nuclear@3 31 static SnapMode snap_mode;
nuclear@0 32
nuclear@0 33 static std::vector<Curve*> curves;
nuclear@2 34 static Curve *sel_curve; // selected curve being edited
nuclear@2 35 static Curve *new_curve; // new curve being entered
nuclear@2 36 static Curve *hover_curve; // curve the mouse is hovering over (click to select)
nuclear@2 37 static int sel_pidx = -1; // selected point of the selected or hovered-over curve
nuclear@0 38
nuclear@2 39 static Label *weight_label; // floating label for the cp weight
nuclear@0 40
nuclear@3 41 #ifdef DRAW_MOUSE_POINTER
nuclear@3 42 static Vector2 mouse_pointer;
nuclear@3 43 #endif
nuclear@3 44
nuclear@0 45
nuclear@0 46 bool app_init(int argc, char **argv)
nuclear@0 47 {
nuclear@5 48 glewInit();
nuclear@5 49
nuclear@0 50 glEnable(GL_MULTISAMPLE);
nuclear@0 51 glEnable(GL_CULL_FACE);
nuclear@2 52
nuclear@2 53 glEnable(GL_BLEND);
nuclear@2 54 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
nuclear@0 55 return true;
nuclear@0 56 }
nuclear@0 57
nuclear@0 58 void app_cleanup()
nuclear@0 59 {
nuclear@0 60 for(size_t i=0; i<curves.size(); i++) {
nuclear@0 61 delete curves[i];
nuclear@0 62 }
nuclear@0 63 curves.clear();
nuclear@0 64 }
nuclear@0 65
nuclear@0 66 void app_draw()
nuclear@0 67 {
nuclear@1 68 glClearColor(0.1, 0.1, 0.1, 1);
nuclear@0 69 glClear(GL_COLOR_BUFFER_BIT);
nuclear@0 70
nuclear@0 71 glMatrixMode(GL_MODELVIEW);
nuclear@0 72 glLoadIdentity();
nuclear@3 73 glTranslatef(view_pan.x * view_scale, view_pan.y * view_scale, 0);
nuclear@0 74 glScalef(view_scale, view_scale, view_scale);
nuclear@0 75
nuclear@3 76 float max_aspect = std::max(win_aspect, 1.0f / win_aspect);
nuclear@3 77 draw_grid(max_aspect, grid_size);
nuclear@0 78
nuclear@0 79 for(size_t i=0; i<curves.size(); i++) {
nuclear@0 80 draw_curve(curves[i]);
nuclear@0 81 }
nuclear@0 82 if(new_curve) {
nuclear@0 83 draw_curve(new_curve);
nuclear@0 84 }
nuclear@3 85
nuclear@3 86 #ifdef DRAW_MOUSE_POINTER
nuclear@3 87 glPointSize(6.0);
nuclear@3 88 glBegin(GL_POINTS);
nuclear@3 89 glColor3f(0, 0, 1);
nuclear@3 90 glVertex2f(mouse_pointer.x, mouse_pointer.y);
nuclear@3 91 glEnd();
nuclear@3 92 #endif
nuclear@4 93
nuclear@4 94 glMatrixMode(GL_MODELVIEW);
nuclear@4 95 glLoadIdentity();
nuclear@4 96
nuclear@4 97 if(weight_label) {
nuclear@4 98 weight_label->draw();
nuclear@4 99 }
nuclear@0 100 }
nuclear@0 101
nuclear@0 102 static void draw_grid(float sz, float sep, float alpha)
nuclear@0 103 {
nuclear@0 104 float x = 0.0f;
nuclear@3 105 float s = 1.0 / view_scale;
nuclear@3 106
nuclear@3 107 sz *= s;
nuclear@3 108 sz += sep; // one more step for when we have non-zero fractional pan
nuclear@3 109 float end = std::min(sz, 100.0f * sep);
nuclear@3 110
nuclear@3 111 // fractional pan
nuclear@3 112 Vector2 pan = view_pan;
nuclear@3 113 Vector2 fpan = Vector2(fmod(pan.x, sep), fmod(pan.y, sep));
nuclear@3 114 Vector2 offset = fpan - pan;
nuclear@3 115
nuclear@3 116 glMatrixMode(GL_MODELVIEW);
nuclear@3 117 glPushMatrix();
nuclear@3 118 glTranslatef(offset.x, offset.y, 0);
nuclear@3 119
nuclear@3 120 glBegin(GL_LINES);
nuclear@3 121 glColor4f(0.35, 0.35, 0.35, alpha);
nuclear@3 122 while(x <= end) {
nuclear@3 123 glVertex2f(-end, x);
nuclear@3 124 glVertex2f(end, x);
nuclear@3 125 glVertex2f(-end, -x);
nuclear@3 126 glVertex2f(end, -x);
nuclear@3 127 glVertex2f(x, -end);
nuclear@3 128 glVertex2f(x, end);
nuclear@3 129 glVertex2f(-x, -end);
nuclear@3 130 glVertex2f(-x, end);
nuclear@3 131 x += sep;
nuclear@3 132 }
nuclear@3 133 glEnd();
nuclear@3 134 glPopMatrix();
nuclear@3 135
nuclear@0 136
nuclear@0 137 glLineWidth(1.0);
nuclear@0 138 glBegin(GL_LINES);
nuclear@0 139 glColor4f(0.6, 0.3, 0.2, alpha);
nuclear@3 140 glVertex2f(-sz + offset.x, 0);
nuclear@3 141 glVertex2f(sz + offset.x, 0);
nuclear@0 142 glColor4f(0.2, 0.3, 0.6, alpha);
nuclear@3 143 glVertex2f(0, -sz + offset.y);
nuclear@3 144 glVertex2f(0, sz + offset.y);
nuclear@0 145 glEnd();
nuclear@3 146
nuclear@0 147 }
nuclear@0 148
nuclear@0 149 static void draw_curve(const Curve *curve)
nuclear@0 150 {
nuclear@2 151 int numpt = curve->size();
nuclear@2 152 int segm = numpt * 16;
nuclear@0 153
nuclear@2 154 glLineWidth(curve == hover_curve ? 4.0 : 2.0);
nuclear@1 155 if(curve == sel_curve) {
nuclear@1 156 glColor3f(0.3, 0.4, 1.0);
nuclear@1 157 } else if(curve == new_curve) {
nuclear@1 158 glColor3f(1.0, 0.75, 0.3);
nuclear@1 159 } else {
nuclear@2 160 glColor3f(0.6, 0.6, 0.6);
nuclear@1 161 }
nuclear@0 162 glBegin(GL_LINE_STRIP);
nuclear@0 163 for(int i=0; i<segm; i++) {
nuclear@0 164 float t = (float)i / (float)(segm - 1);
nuclear@0 165 Vector2 v = curve->interpolate(t);
nuclear@0 166 glVertex2f(v.x, v.y);
nuclear@0 167 }
nuclear@0 168 glEnd();
nuclear@0 169 glLineWidth(1.0);
nuclear@0 170
nuclear@2 171 glPointSize(curve == hover_curve ? 10.0 : 7.0);
nuclear@0 172 glBegin(GL_POINTS);
nuclear@1 173 if(curve == new_curve) {
nuclear@1 174 glColor3f(1.0, 0.0, 0.0);
nuclear@1 175 } else {
nuclear@1 176 glColor3f(0.6, 0.3, 0.2);
nuclear@1 177 }
nuclear@0 178 for(int i=0; i<numpt; i++) {
nuclear@1 179 if(curve == sel_curve) {
nuclear@1 180 if(i == sel_pidx) {
nuclear@1 181 glColor3f(1.0, 0.2, 0.1);
nuclear@1 182 } else {
nuclear@1 183 glColor3f(0.2, 1.0, 0.2);
nuclear@1 184 }
nuclear@1 185 }
nuclear@0 186 Vector2 pt = curve->get_point(i);
nuclear@0 187 glVertex2f(pt.x, pt.y);
nuclear@0 188 }
nuclear@0 189 glEnd();
nuclear@0 190 glPointSize(1.0);
nuclear@0 191 }
nuclear@0 192
nuclear@0 193 void app_reshape(int x, int y)
nuclear@0 194 {
nuclear@0 195 win_width = x;
nuclear@0 196 win_height = y;
nuclear@0 197 win_aspect = (float)x / (float)y;
nuclear@0 198
nuclear@0 199 glViewport(0, 0, x, y);
nuclear@0 200 glMatrixMode(GL_PROJECTION);
nuclear@0 201 glLoadIdentity();
nuclear@0 202 glOrtho(-win_aspect, win_aspect, -1, 1, -1, 1);
nuclear@0 203 }
nuclear@0 204
nuclear@0 205 void app_keyboard(int key, bool pressed)
nuclear@0 206 {
nuclear@0 207 if(pressed) {
nuclear@0 208 switch(key) {
nuclear@1 209 case 'q':
nuclear@1 210 case 'Q':
nuclear@1 211 exit(0);
nuclear@1 212
nuclear@0 213 case 27:
nuclear@1 214 if(new_curve) {
nuclear@1 215 delete new_curve;
nuclear@1 216 new_curve = 0;
nuclear@1 217 post_redisplay();
nuclear@1 218 }
nuclear@1 219 break;
nuclear@0 220
nuclear@0 221 case 'l':
nuclear@0 222 case 'L':
nuclear@0 223 if(sel_curve) {
nuclear@0 224 sel_curve->set_type(CURVE_LINEAR);
nuclear@0 225 post_redisplay();
nuclear@0 226 }
nuclear@1 227 if(new_curve) {
nuclear@1 228 new_curve->set_type(CURVE_LINEAR);
nuclear@1 229 post_redisplay();
nuclear@1 230 }
nuclear@0 231 break;
nuclear@0 232
nuclear@0 233 case 'b':
nuclear@0 234 case 'B':
nuclear@0 235 if(sel_curve) {
nuclear@0 236 sel_curve->set_type(CURVE_BSPLINE);
nuclear@0 237 post_redisplay();
nuclear@0 238 }
nuclear@1 239 if(new_curve) {
nuclear@1 240 new_curve->set_type(CURVE_BSPLINE);
nuclear@1 241 post_redisplay();
nuclear@1 242 }
nuclear@0 243 break;
nuclear@0 244
nuclear@0 245 case 'h':
nuclear@0 246 case 'H':
nuclear@0 247 if(sel_curve) {
nuclear@0 248 sel_curve->set_type(CURVE_HERMITE);
nuclear@0 249 post_redisplay();
nuclear@0 250 }
nuclear@1 251 if(new_curve) {
nuclear@1 252 new_curve->set_type(CURVE_HERMITE);
nuclear@1 253 post_redisplay();
nuclear@1 254 }
nuclear@0 255 break;
nuclear@6 256
nuclear@6 257 case 'e':
nuclear@6 258 case 'E':
nuclear@6 259 // TODO: GUI for filename at least
nuclear@10 260 if(!save_curves("test.curves", &curves[0], (int)curves.size())) {
nuclear@6 261 fprintf(stderr, "failed to export curves\n");
nuclear@6 262 }
nuclear@6 263 printf("exported %d curves\n", (int)curves.size());
nuclear@6 264 break;
nuclear@10 265
nuclear@10 266 case 'i':
nuclear@10 267 case 'I':
nuclear@10 268 {
nuclear@10 269 std::list<Curve*> clist = load_curves("test.curves");
nuclear@10 270 if(clist.empty()) {
nuclear@10 271 fprintf(stderr, "failed to import curves\n");
nuclear@10 272 }
nuclear@10 273
nuclear@10 274 int num = 0;
nuclear@10 275 std::list<Curve*>::iterator it = clist.begin();
nuclear@10 276 while(it != clist.end()) {
nuclear@10 277 curves.push_back(*it++);
nuclear@10 278 ++num;
nuclear@10 279 }
nuclear@10 280 printf("imported %d curves\n", num);
nuclear@10 281 }
nuclear@10 282 break;
nuclear@0 283 }
nuclear@0 284 }
nuclear@3 285
nuclear@3 286
nuclear@3 287 switch(key) {
nuclear@3 288 case 's':
nuclear@3 289 snap_mode = pressed ? SNAP_GRID : SNAP_NONE;
nuclear@3 290 break;
nuclear@3 291
nuclear@3 292 case 'S':
nuclear@3 293 snap_mode = pressed ? SNAP_POINT : SNAP_NONE;
nuclear@3 294 break;
nuclear@3 295
nuclear@3 296 default:
nuclear@3 297 break;
nuclear@3 298 }
nuclear@3 299 }
nuclear@3 300
nuclear@3 301 static void calc_view_matrix()
nuclear@3 302 {
nuclear@3 303 view_matrix.reset_identity();
nuclear@3 304 view_matrix.scale(Vector3(view_scale, view_scale, view_scale));
nuclear@3 305 view_matrix.translate(Vector3(view_pan.x, view_pan.y, 0.0));
nuclear@0 306 }
nuclear@0 307
nuclear@0 308 static Vector2 pixel_to_uv(int x, int y)
nuclear@0 309 {
nuclear@0 310 float u = win_aspect * (2.0 * (float)x / (float)win_width - 1.0);
nuclear@0 311 float v = 1.0 - 2.0 * (float)y / (float)win_height;
nuclear@3 312
nuclear@3 313 u = u / view_scale - view_pan.x;
nuclear@3 314 v = v / view_scale - view_pan.y;
nuclear@0 315 return Vector2(u, v);
nuclear@3 316 /*
nuclear@3 317 Matrix4x4 inv_view_matrix = view_matrix.inverse();
nuclear@3 318 Vector4 res = Vector4(u, v, 0.0, 1.0).transformed(inv_view_matrix);
nuclear@3 319
nuclear@3 320 return Vector2(res.x, res.y);
nuclear@3 321 */
nuclear@0 322 }
nuclear@0 323
nuclear@0 324 static int prev_x, prev_y;
nuclear@0 325 static int click_pos[8][2];
nuclear@0 326 static unsigned int bnstate;
nuclear@0 327
nuclear@0 328 #define BNBIT(x) (1 << (x))
nuclear@0 329
nuclear@0 330 void app_mouse_button(int bn, bool pressed, int x, int y)
nuclear@0 331 {
nuclear@0 332 prev_x = x;
nuclear@0 333 prev_y = y;
nuclear@0 334 if(pressed) {
nuclear@0 335 bnstate |= BNBIT(bn);
nuclear@0 336 } else {
nuclear@0 337 bnstate &= ~BNBIT(bn);
nuclear@0 338 }
nuclear@0 339
nuclear@0 340 if(pressed) {
nuclear@0 341 click_pos[bn][0] = x;
nuclear@0 342 click_pos[bn][1] = y;
nuclear@0 343 } else {
nuclear@0 344 int dx = x - click_pos[bn][0];
nuclear@0 345 int dy = y - click_pos[bn][1];
nuclear@0 346
nuclear@0 347 if(abs(dx) + abs(dy) < 3) {
nuclear@0 348 Vector2 uv = pixel_to_uv(x, y);
nuclear@0 349 on_click(bn, uv.x, uv.y);
nuclear@0 350 }
nuclear@0 351
nuclear@0 352 if(!(bnstate & BNBIT(2))) {
nuclear@0 353 delete weight_label;
nuclear@0 354 weight_label = 0;
nuclear@0 355 post_redisplay();
nuclear@0 356 }
nuclear@0 357 }
nuclear@0 358 }
nuclear@0 359
nuclear@2 360 static bool point_hit_test(const Vector2 &pos, Curve **curveret, int *pidxret)
nuclear@0 361 {
nuclear@3 362 float thres = 0.02 / view_scale;
nuclear@0 363
nuclear@0 364 for(size_t i=0; i<curves.size(); i++) {
nuclear@2 365 int pidx = curves[i]->nearest_point(pos);
nuclear@0 366 if(pidx == -1) continue;
nuclear@0 367
nuclear@0 368 Vector2 cp = curves[i]->get_point(pidx);
nuclear@2 369 if((cp - pos).length_sq() < thres * thres) {
nuclear@2 370 *curveret = curves[i];
nuclear@2 371 *pidxret = pidx;
nuclear@2 372 return true;
nuclear@0 373 }
nuclear@0 374 }
nuclear@2 375 *curveret = 0;
nuclear@2 376 *pidxret = -1;
nuclear@2 377 return false;
nuclear@0 378 }
nuclear@0 379
nuclear@3 380 static Vector2 snap(const Vector2 &p)
nuclear@3 381 {
nuclear@3 382 switch(snap_mode) {
nuclear@3 383 case SNAP_GRID:
nuclear@3 384 return Vector2(round(p.x / grid_size) * grid_size, round(p.y / grid_size) * grid_size);
nuclear@3 385 case SNAP_POINT:
nuclear@3 386 // TODO
nuclear@3 387 default:
nuclear@3 388 break;
nuclear@3 389 }
nuclear@3 390 return p;
nuclear@3 391 }
nuclear@3 392
nuclear@0 393 void app_mouse_motion(int x, int y)
nuclear@0 394 {
nuclear@3 395 Vector2 prev_uv = pixel_to_uv(prev_x, prev_y);
nuclear@3 396
nuclear@0 397 int dx = x - prev_x;
nuclear@0 398 int dy = y - prev_y;
nuclear@0 399 prev_x = x;
nuclear@0 400 prev_y = y;
nuclear@0 401
nuclear@0 402 if(!dx && !dy) return;
nuclear@0 403
nuclear@2 404 Vector2 uv = pixel_to_uv(x, y);
nuclear@3 405 #ifdef DRAW_MOUSE_POINTER
nuclear@3 406 mouse_pointer = uv;
nuclear@3 407 post_redisplay();
nuclear@3 408 #endif
nuclear@2 409
nuclear@2 410 /* when entering a new curve, have the last (extra) point following
nuclear@2 411 * the mouse until it's entered by a click (see on_click).
nuclear@2 412 */
nuclear@3 413 if(new_curve) {
nuclear@3 414 new_curve->move_point(new_curve->size() - 1, snap(uv));
nuclear@2 415 post_redisplay();
nuclear@2 416 }
nuclear@2 417
nuclear@4 418 if(!new_curve && !bnstate) {
nuclear@3 419 // not dragging, highlight curve under mouse
nuclear@2 420 point_hit_test(uv, &hover_curve, &sel_pidx);
nuclear@0 421 post_redisplay();
nuclear@0 422
nuclear@3 423 } else {
nuclear@3 424 // we're dragging with one or more buttons held down
nuclear@0 425
nuclear@3 426 if(sel_curve && sel_pidx != -1) {
nuclear@3 427 // we have a curve and a point of the curve selected
nuclear@0 428
nuclear@3 429 if(bnstate & BNBIT(0)) {
nuclear@3 430 // dragging point with left button: move it
nuclear@3 431 sel_curve->move_point(sel_pidx, snap(uv));
nuclear@3 432 post_redisplay();
nuclear@0 433 }
nuclear@3 434
nuclear@3 435 if(bnstate & BNBIT(2)) {
nuclear@3 436 // dragging point with right button: change weight
nuclear@3 437 float w = sel_curve->get_weight(sel_pidx);
nuclear@3 438 w -= dy * 0.01;
nuclear@3 439 if(w < FLT_MIN) w = FLT_MIN;
nuclear@3 440 sel_curve->set_weight(sel_pidx, w);
nuclear@3 441
nuclear@3 442 // popup floating weight label if not already there
nuclear@3 443 if(!weight_label) {
nuclear@3 444 weight_label = new Label;
nuclear@3 445 }
nuclear@3 446 weight_label->set_position(uv);
nuclear@3 447 weight_label->set_textf("w=%g", w);
nuclear@3 448 post_redisplay();
nuclear@3 449 }
nuclear@3 450 } else {
nuclear@3 451 // no selection, we're dragging in empty space: manipulate viewport
nuclear@3 452 Vector2 dir = uv - prev_uv;
nuclear@3 453
nuclear@3 454 if(bnstate & (BNBIT(0) | BNBIT(1))) {
nuclear@3 455 // panning
nuclear@3 456 view_pan += dir;
nuclear@3 457 calc_view_matrix();
nuclear@3 458 post_redisplay();
nuclear@3 459 }
nuclear@3 460 if(bnstate & BNBIT(2)) {
nuclear@3 461 // zooming
nuclear@3 462 view_scale -= ((float)dy / (float)win_height) * view_scale * 5.0;
nuclear@3 463 if(view_scale < 1e-4) view_scale = 1e-4;
nuclear@3 464 calc_view_matrix();
nuclear@3 465 post_redisplay();
nuclear@3 466 }
nuclear@0 467 }
nuclear@0 468 }
nuclear@0 469 }
nuclear@0 470
nuclear@0 471 static void on_click(int bn, float u, float v)
nuclear@0 472 {
nuclear@2 473 Vector2 uv = Vector2(u, v);
nuclear@2 474
nuclear@0 475 switch(bn) {
nuclear@2 476 case 0: // ------- LEFT CLICK ------
nuclear@2 477 if(hover_curve) {
nuclear@2 478 // if we're hovering: click selects
nuclear@2 479 sel_curve = hover_curve;
nuclear@2 480 hover_curve = 0;
nuclear@2 481 } else if(sel_curve) {
nuclear@2 482 // if we have a selected curve: click adds point (enter new_curve mode)
nuclear@2 483 std::vector<Curve*>::iterator it = std::find(curves.begin(), curves.end(), sel_curve);
nuclear@2 484 assert(it != curves.end());
nuclear@2 485 curves.erase(it, it + 1);
nuclear@2 486
nuclear@2 487 new_curve = sel_curve;
nuclear@2 488 sel_curve = 0;
nuclear@2 489 sel_pidx = -1;
nuclear@2 490
nuclear@2 491 new_curve->add_point(uv);
nuclear@2 492 } else {
nuclear@2 493 // otherwise, click starts a new curve
nuclear@2 494 if(!new_curve) {
nuclear@2 495 new_curve = new Curve;
nuclear@2 496 new_curve->add_point(uv);
nuclear@2 497 }
nuclear@2 498 new_curve->add_point(uv);
nuclear@0 499 }
nuclear@0 500 post_redisplay();
nuclear@0 501 break;
nuclear@0 502
nuclear@2 503 case 2: // ------- RIGHT CLICK ------
nuclear@2 504 if(new_curve) {
nuclear@2 505 // in new-curve mode: finish curve (cancels last floating segment)
nuclear@2 506 new_curve->remove_point(new_curve->size() - 1);
nuclear@2 507 if(new_curve->empty()) {
nuclear@2 508 delete new_curve;
nuclear@2 509 } else {
nuclear@2 510 curves.push_back(new_curve);
nuclear@2 511 }
nuclear@2 512 new_curve = 0;
nuclear@2 513
nuclear@2 514 } else if(sel_curve) {
nuclear@2 515 // in selected curve mode: delete control point or unselect
nuclear@2 516 Curve *hit_curve;
nuclear@2 517 int hit_pidx;
nuclear@2 518 if(point_hit_test(uv, &hit_curve, &hit_pidx) && hit_curve == sel_curve) {
nuclear@2 519 hit_curve->remove_point(hit_pidx);
nuclear@2 520 sel_pidx = -1;
nuclear@2 521 } else {
nuclear@2 522 sel_curve = 0;
nuclear@2 523 sel_pidx = -1;
nuclear@2 524 }
nuclear@2 525 }
nuclear@0 526 post_redisplay();
nuclear@0 527 break;
nuclear@0 528
nuclear@0 529 default:
nuclear@0 530 break;
nuclear@0 531 }
nuclear@0 532 }