/* -*- c++ -*- FILE: EventHandler.cpp RCS REVISION: $Revision: 1.39 $ COPYRIGHT: (c) 1999 -- 2003 Melinda Green, Don Hatch, and Jay Berkenbilt - Superliminal Software LICENSE: Free to use and modify for non-commercial purposes as long as the following conditions are adhered to: 1) Obvious credit for the source of this code and the designs it embodies are clearly made, and 2) Ports and derived versions of 4D Magic Cube programs are not distributed without the express written permission of the authors. DESCRIPTION: Implementation of the EventHandler class */ #include "EventHandler.h" #include #include #include #include "MagicCube.h" #include "Puzzlest.h" #include "Polymgr.h" #include "History.h" #include "Macro.h" #include "Machine.h" #include "Widgets.h" #include "MacroManager.h" #include "PostScriptWriter.h" float const EventHandler::DEF_TWIST_INCREMENT = 0.5; EventHandler::EventHandler(int argc, char **argv, char const* machine_type) : machine(0), widgets(0), polymgr(0), puzzle_state(0), history(0), macromgr(0), creating_a_macro(false), deleting_a_macro(false), next_slicesmask(0), fast_automoves(false), number_of_reference_stickers_needed(0), what_to_do_after_got_reference_stickers(0), cur_ui_data(0), quick_mode(false), dragging(false), nscramblechen(NSCRAMBLECHEN) { this->machine = Machine::createMachine( this, argc, argv, this->preferences, machine_type); int length = this->preferences.getLength(); this->polymgr = new PolygonManager4D(this->preferences); this->puzzle_state = new PuzzleState(this->preferences, this->polymgr); this->history = new History(this->preferences, this->polymgr); this->macromgr = new MacroManager(this->preferences, this->polymgr); this->machine->init(puzzle_state); this->widgets = this->machine->getWidgets(); nscramblechen = this->preferences.getIntProperty(M4D_NSCRAMBLECHEN, NSCRAMBLECHEN); printf("Each little cube is a hypersticker.\n"); printf("Each %dx%dx%d cluster is a hyperface.\n", length, length, length); printf("Middle button rotates a hyperface to center.\n"); printf("Left or right button twists a hyperface.\n"); printf("*** Control-left or control-right twists two layers. ***\n"); printf ("*** Shift-left or shift-right twists the whole hypercube at once. ***\n"); printf ("(Shift or control)-middle and dragging changes the viewing perspective.\n"); printf("Type 'h' for a list of key commands.\n"); widgets->addButton( Widgets::ButtonData("Undo", &EventHandler::undo_cb, (void*)0)); widgets->addButton( Widgets::ButtonData("Redo", &EventHandler::redo_cb, (void*)0)); static Widgets::ButtonData twist_items[] = { Widgets::ButtonData(" 1 twist", &EventHandler::scramble_cb, (void *)1), Widgets::ButtonData(" 2 twists", &EventHandler::scramble_cb, (void *)2), Widgets::ButtonData(" 3 twists", &EventHandler::scramble_cb, (void *)3), Widgets::ButtonData(" 4 twists", &EventHandler::scramble_cb, (void *)4), Widgets::ButtonData(" 5 twists", &EventHandler::scramble_cb, (void *)5), Widgets::ButtonData(" 6 twists", &EventHandler::scramble_cb, (void *)6), Widgets::ButtonData(" 7 twists", &EventHandler::scramble_cb, (void *)7), Widgets::ButtonData(" 8 twists", &EventHandler::scramble_cb, (void *)8), Widgets::ButtonData("Full Scramble", &EventHandler::scramble_cb, (void *)NSCRAMBLECHEN), }; int n_items = sizeof(twist_items) / sizeof(twist_items[0]); assert(n_items == 9); widgets->addMenuButton("Scramble...", n_items, twist_items); widgets->addButton( Widgets::ButtonData("Solve", &EventHandler::cheat_cb, 0)); widgets->addButton( Widgets::ButtonData("Reset", &EventHandler::reset_cb, 0)); static Widgets::ButtonData new_puzzle_items[] = { // widgets_button_create_data( // MAXLENGTH<1 ? NULL : "1x1x1x1", new_puzzle_cb, (void *)1, Widgets::ButtonData(MAXLENGTH < 2 ? 0 : Widgets::ButtonData("2x2x2x2", &EventHandler::newPuzzle_cb, (void *)2)), Widgets::ButtonData(MAXLENGTH < 3 ? 0 : Widgets::ButtonData("3x3x3x3", &EventHandler::newPuzzle_cb, (void *)3)), Widgets::ButtonData(MAXLENGTH < 4 ? 0 : Widgets::ButtonData("4x4x4x4", &EventHandler::newPuzzle_cb, (void *)4)), Widgets::ButtonData(MAXLENGTH < 5 ? 0 : Widgets::ButtonData("5x5x5x5", &EventHandler::newPuzzle_cb, (void *)5)), Widgets::ButtonData(MAXLENGTH < 6 ? 0 : Widgets::ButtonData("6x6x6x6", &EventHandler::newPuzzle_cb, (void *)6)), Widgets::ButtonData(MAXLENGTH < 7 ? 0 : Widgets::ButtonData("7x7x7x7", &EventHandler::newPuzzle_cb, (void *)7)), }; n_items = sizeof(new_puzzle_items) / sizeof(new_puzzle_items[0]); widgets->addMenuButton("New Puzzle...", n_items, new_puzzle_items); widgets->addButton(Widgets::ButtonData("Save", &EventHandler::save_cb, 0)); widgets->addButton(Widgets::ButtonData("Print", &EventHandler::print_cb, 0)); widgets->addButton(Widgets::ButtonData("Quit", &EventHandler::quit_cb, 0)); readLogfile(this->preferences.getStringProperty(M4D_LOGFILE)); void* prev_ui_data = 0; for (int i = 0; i < macromgr->getNMacros(); ++i) { void* ui_data = widgets->addMacro( macromgr->getMacroName(i), prev_ui_data); macromgr->setUIData(i, ui_data); prev_ui_data = ui_data; } polymgr->getUntwistedFrame(&untwisted_frame); /* an expose event will happen immediately */ machine->addEventHandler(1 << EXPOSE, &EventHandler::expose_handler, 0); machine->addEventHandler(1 << RESIZE, &EventHandler::expose_handler, 0); machine->addEventHandler(1 << BUTTONDOWN, &EventHandler::buttonDown_handler, 0); machine->addEventHandler(1 << KEYPRESS, &EventHandler::keyPress_handler, 0); machine->addEventHandler(1 << KEYRELEASE, &EventHandler::keyRelease_handler, 0); machine->addEventHandler(1 << DRAG, &EventHandler::drag_handler, 0); } EventHandler::Callback* EventHandler::createCallback(callback_fn fn, void* arg) { return new Callback(this, fn, arg); } void EventHandler::run() { machine->eventLoop(); } EventHandler::~EventHandler() { // this->widgets is under external control delete this->machine; delete this->polymgr; delete this->puzzle_state; delete this->history; delete this->macromgr; } void EventHandler::readLogfile(char *filename) { // FIX THIS: this code is duplicated between the windows and UNIX // sections of the code. FILE *fp; if ((fp = fopen(filename, "r"))) { // The UNIX code doesn't currently have a notion of scramble // state. This is implemented in MagicCubeObj for the Windows // version, but that object has some Windows-specific stuff in // it. For now, we just ignore scramble_state and user_twists // when validating the header. int error = 0; // Length must one more than field length in fscanf below char magic_number[20]; magic_number[19] = '\0'; int file_version; long int scramble_state; int user_twists; fscanf(fp, "%19s %d%ld%d", magic_number, &file_version, &scramble_state, &user_twists); if (!((file_version == MAGICCUBE_FILE_VERSION) && (strcmp(magic_number, MAGIC_NUMBER) == 0))) { fprintf(stderr, "%s does not look like a MagicCube4D log file", filename); error = 1; } else { if (! this->puzzle_state->read(fp)) { error = 1; fprintf(stderr, "Couldn't read the state from %s!\n", filename); } else if (! this->history->read(fp)) { error = 1; fprintf(stderr, "Couldn't read the history from %s!\n", filename); } else if (! this->macromgr->read(fp)) { error = 1; fprintf(stderr, "Couldn't read the macros from %s!\n", filename); } } fclose(fp); if (error) { exit(2); } } } /* * Return 1 on success, 0 on failure */ void EventHandler::dump(FILE *fp) { // FIX THIS: this code is duplicated between the windows and UNIX // sections of the code. // The second and third values of the header are supposed to be // the scramble state and the number of twists done by the user // (as opposed to the software during scrambling). The Windows // code keeps track of these things in MagicCubeObj which // currently contains some Windows-specific code. That object // should be rewritten so that the non-Windows specific stuff can // become common. For now, rather than duplicating the logic // here, we will just pretend that all twists are user twists and // that the scramble state is NONE (which we know has the value // 0). That will almost never be exactly correct, but it will at // least allow for exchange of log files between Windows and UNIX // users. int twists = history->countTwists(); fprintf(fp, "%s %d 0 %d\n", MAGIC_NUMBER, MAGICCUBE_FILE_VERSION, twists); this->puzzle_state->dump(fp); this->history->dump(fp); this->macromgr->dump(fp); } bool EventHandler::writeLogfile(char *filename) { bool result = false; FILE *fp; if ((fp = fopen(filename, "w"))) { dump(fp); fclose(fp); result = true; } return result; } void EventHandler::expose_handler(Event*, void*) { this->machine->drawFrame(&untwisted_frame); } void EventHandler::showAnimation(struct stickerspec *grip, int dir, int slicesmask) { int seqno, outof; outof = (quick_mode ? 1 : polymgr->getTwistNFrames(grip)); if (preferences.getBoolProperty(M4D_ALL_AT_ONCE)) { struct frame *frames = new struct frame[outof]; for (seqno = 1; seqno <= outof; ++seqno) polymgr->getFrame(grip, dir, slicesmask, seqno, outof, &frames[seqno - 1]); for (seqno = 1; seqno <= outof; ++seqno) machine->drawFrame(&frames[seqno - 1]); delete [] frames; } else { struct frame frame; for (seqno = 1; seqno <= outof; ++seqno) { polymgr->getFrame(grip, dir, slicesmask, seqno, outof, &frame); machine->drawFrame(&frame); } } } void EventHandler::getAReferenceSticker(Event *event, void *) { struct stickerspec sticker; /* FIX THIS-- change the dialog box to say how many still needed */ if (number_of_reference_stickers_needed > 0) { if (!polymgr->pick(event->x, event->y, &untwisted_frame, &sticker)) { /* printf("missed!\n"); */ machine->bell(); return; } SET4(reference_stickers_needed[0], sticker.coords); number_of_reference_stickers_needed--; reference_stickers_needed++; if (number_of_reference_stickers_needed == 0) (this->*what_to_do_after_got_reference_stickers)(); } } void EventHandler::buttonDown_handler(Event *event, void *arg) { struct stickerspec grip; int x, y; int length = this->preferences.getLength(); if (number_of_reference_stickers_needed) { getAReferenceSticker(event, arg); return; } if (event->button == MIDDLEBUTTON) if (event->shift_is_down || event->control_is_down) { dragging = 1; return; } else dragging = 0; x = event->x; y = event->y; if (polymgr->pickGrip(x, y, &untwisted_frame, &grip)) { int slicesmask = ~0; int dir = CCW; switch (event->button) { case LEFTBUTTON: case RIGHTBUTTON: if (grip.dim == 3) { fprintf(stderr, "Can't twist that.\n"); return; } if (event->shift_is_down) if (event->control_is_down) slicesmask = ~(1 << (length - 1)); /* all but last */ else slicesmask = ~0; /* everything */ else if (event->control_is_down) slicesmask = (1 << 0) | (1 << 1); /* two layers */ else if (next_slicesmask) slicesmask = next_slicesmask; /* overridden value */ else slicesmask = 1; /* one layer */ dir = ((event->button == LEFTBUTTON) ? CCW : CW); break; case MIDDLEBUTTON: if (!polymgr->facetocenterToGrip(grip.face, &grip)) { fprintf(stderr, "Can't rotate that to center.\n"); return; } slicesmask = ~0; dir = CCW; break; } showAnimation(&grip, dir, slicesmask); puzzle_state->applyMove(&grip, dir, slicesmask); if (preferences.getBoolProperty(M4D_DRAW_NEW_STATE)) machine->drawFrame(&untwisted_frame); history->apply(&grip, dir, slicesmask); macromgr->addMove(&grip, dir, slicesmask); /* doesn't hurt */ } else { /* printf("%d,%d:", x, y); */ /* printf("missed!\n"); */ machine->bell(); } } /* * Button callback functions... */ void EventHandler::undo_cb(void* argp) { int arg = (int) argp; struct stickerspec grip; int dir; int slicesmask; if (creating_a_macro) { /* FIX THIS */ printf("Sorry, can't undo while defining a macro\n"); return; } int handled = 0; if (fast_automoves && history->atMacroClose()) { struct stickerspec sticker; int dir; int slicesmask; while (history->goTowardsMark(MARK_MACRO_OPEN, &sticker, &dir, &slicesmask) == 1) { handled = 1; puzzle_state->applyMove(&sticker, dir, slicesmask); } machine->drawFrame(&untwisted_frame); } else if ((arg == 0) && (history->atScrambleBoundary())) { handled = 1; printf("\aUse shift-U to undo past a scramble boundary\n"); } if (!handled) { if (history->undo(&grip, &dir, &slicesmask)) { showAnimation(&grip, dir, slicesmask); puzzle_state->applyMove(&grip, dir, slicesmask); if (preferences.getBoolProperty(M4D_DRAW_NEW_STATE)) machine->drawFrame(&untwisted_frame); } else printf("Nothing to undo.\n"); } } void EventHandler::redo_cb(void* argp) { int arg = (int) argp; struct stickerspec grip; int dir; int slicesmask; if (creating_a_macro) { /* FIX THIS */ printf("Sorry, can't redo while defining a macro\n"); return; } int handled = 0; if (fast_automoves && history->atMacroOpen()) { struct stickerspec sticker; int dir; int slicesmask; while (history->goTowardsMark(MARK_MACRO_CLOSE, &sticker, &dir, &slicesmask) == 1) { handled = 1; puzzle_state->applyMove(&sticker, dir, slicesmask); } machine->drawFrame(&untwisted_frame); } else if ((arg == 0) && (history->atScrambleBoundary())) { handled = 1; printf("\aUse shift-R to redo past a scramble boundary\n"); } if (!handled) { if (history->redo(&grip, &dir, &slicesmask)) { showAnimation(&grip, dir, slicesmask); puzzle_state->applyMove(&grip, dir, slicesmask); if (preferences.getBoolProperty(M4D_DRAW_NEW_STATE)) machine->drawFrame(&untwisted_frame); } else printf("Nothing to redo.\n"); } } void EventHandler::scramble_cb(void *arg = NULL) { int n = (int)arg; struct stickerspec grip; int i, previous_face = -1; int ngrips = NFACES * 3 * 3 * 3; int max_slicesmask = preferences.getLength() / 2; bool full_scramble = (n == nscramblechen); if (full_scramble) { reset_cb(NULL); } for (i = 0; i < n; ++i) { do { grip.id_within_cube = rand() % ngrips; polymgr->fillStickerspecFromIdAndLength(&grip, 3); } while (grip.dim != 2 || i > 0 && grip.face == previous_face || i > 0 && grip.face == polymgr->oppositeFace(previous_face)); int slicesmask = 1; if (max_slicesmask > 1) { slicesmask += rand() % max_slicesmask; } previous_face = grip.face; puzzle_state->applyMove(&grip, CCW, slicesmask); history->apply(&grip, CCW, slicesmask); } if (full_scramble) { history->mark(MARK_SCRAMBLE_BOUNDARY); } machine->drawFrame(&untwisted_frame); } void EventHandler::cheat_cb(void *) { struct stickerspec grip; int dir; int slicesmask; if (creating_a_macro) { /* FIX THIS */ printf("Sorry, can't solve while defining a macro\n"); return; } history->compress(); while ((! puzzle_state->isSolved()) && history->undo(&grip, &dir, &slicesmask)) { showAnimation(&grip, dir, slicesmask); puzzle_state->applyMove(&grip, dir, slicesmask); if (preferences.getBoolProperty(M4D_DRAW_NEW_STATE)) machine->drawFrame(&untwisted_frame); } history->clear(); } void EventHandler::reset_cb(void *) { polymgr->reset(); puzzle_state->reset(); history->reset(); machine->drawFrame(&untwisted_frame); } void EventHandler::save_cb(void *) { int i; for (i = 0; i < macromgr->getNMacros(); ++i) { char* name = widgets->getMacroName(i); if (name) { macromgr->setMacroName(i, name); } } if (!writeLogfile(this->preferences.getStringProperty(M4D_LOGFILE))) { /* FIX THIS-- need better recovery if can't write the file */ machine->bell(); fprintf(stderr, "unable to save; saving to stderr (between ---'s)\n"); fprintf(stderr, "---\n"); dump(stderr); fprintf(stderr, "---\n"); } } void EventHandler::quit_cb(void *) { if (creating_a_macro) cancelAdd_cb(); save_cb(0); exit(0); } void EventHandler::print_cb(void *) { assert(this->puzzle_state != 0); PostScriptWriter psw(this->preferences, *this->puzzle_state, untwisted_frame); char filename[100]; sprintf(filename, "/tmp/mc4d.%d.EPS", getpid()); if (psw.generateOutput(filename)) { std::cout << "wrote PostScript to " << filename << std::endl; } } void EventHandler::cancelApplyMacro_cb(void *) { widgets->destroyDialog(); } void EventHandler::applyMacroCBAfterGotRefStickers(void *) { struct stickerspec grip; int dir; int slicesmask; widgets->destroyDialog(); if (macromgr->open(macro_which, nrefs, refs, MacroManager::m_reading, macro_invert ? -1 : 1)) { /* printf("Executing macro \"%s\"...", macro_getname(name)); */ fflush(stdout); if (! history->atMacroOpen()) { // FIX THIS -- Must insert this mark twice because // truncate eats one of them. If truncate doesn't eat one // of them, then undoing a macro and immediately doing // anything else leaves a stray m[ in the log file. history->mark(MARK_MACRO_OPEN); } history->mark(MARK_MACRO_OPEN); while (macromgr->getMove(&grip, &dir, &slicesmask)) { if (!fast_automoves) { showAnimation(&grip, dir, slicesmask); } puzzle_state->applyMove(&grip, dir, slicesmask); if (preferences.getBoolProperty(M4D_DRAW_NEW_STATE)) machine->drawFrame(&untwisted_frame); history->apply(&grip, dir, slicesmask); macromgr->addMove(&grip, dir, slicesmask); // allow nested macros } macromgr->close(); history->mark(MARK_MACRO_CLOSE); if (fast_automoves) { machine->drawFrame(&untwisted_frame); } /* printf(" done.\n"); */ } else { std::cout << "Sorry, the original reference stickers can't be rotated " << "to those. (aborted)" << std::endl; /* FIX THIS--- make it an error dialog */ widgets->destroyDialog(); } } void EventHandler::applyMacroCommon(void *ui_data, bool invert) { // ui_data == 0 means there's only one macro macro_which = (ui_data ? this->macromgr->findWithUIData(ui_data) : 0); macro_invert = invert; if (deleting_a_macro) { widgets->destroyDialog(); void* ui_data = macromgr->destroy(macro_which); widgets->removeMacro(ui_data); deleting_a_macro = false; return; } static Widgets::ButtonData button( "Cancel", &EventHandler::cancelApplyMacro_cb, 0); widgets->createDialog("Click on three reference stickers.\n", 1, &button); number_of_reference_stickers_needed = 3; reference_stickers_needed = refs; what_to_do_after_got_reference_stickers = &EventHandler::applyMacroCBAfterGotRefStickers; } void EventHandler::applyMacro_cb(void *w) { applyMacroCommon(w, 0); } void EventHandler::invertMacro_cb(void *w) { applyMacroCommon(w, 1); } void EventHandler::cancelAdd_cb(void *) { int which = macromgr->getNMacros() - 1; widgets->destroyDialog(); widgets->removeMacro(cur_ui_data); this->cur_ui_data = 0; macromgr->close(); macromgr->destroy(which); creating_a_macro = false; } void EventHandler::doneAdd_cb(void *) { widgets->destroyDialog(); macromgr->setUIData(macromgr->getNMacros() - 1, cur_ui_data); this->cur_ui_data = 0; macromgr->close(); creating_a_macro = false; } void EventHandler::add_cb(void *) { if (creating_a_macro) return; creating_a_macro = true; deleting_a_macro = false; void* prev_ui_data = 0; if (macromgr->getNMacros() > 0) { prev_ui_data = macromgr->getUIData(macromgr->getNMacros() - 1); } macromgr->create("", nrefs, refs); macromgr->open(macromgr->getNMacros() - 1, nrefs, refs, MacroManager::m_writing, 0); widgets->destroyDialog(); this->cur_ui_data = widgets->addMacro("", prev_ui_data); static Widgets::ButtonData button("Cancel", &EventHandler::cancelAdd_cb, 0); widgets->createDialog("Click on three\nreference stickers.\n", 1, &button); number_of_reference_stickers_needed = 3; reference_stickers_needed = refs; what_to_do_after_got_reference_stickers = &EventHandler::addCBAfterGotRefStickers; } void EventHandler::addCBAfterGotRefStickers(void *) { widgets->destroyDialog(); static Widgets::ButtonData make_macro[2] = { Widgets::ButtonData("Done", &EventHandler::doneAdd_cb, 0), Widgets::ButtonData("Cancel", &EventHandler::cancelAdd_cb, 0), }; // FIX THIS-- it obscures the window! // not if macros are on the right. -ejb widgets->createDialog ("Enter the moves of the macro,\nthen click on \"Done\".", 2, make_macro); macromgr->setMacroRefs(macromgr->getNMacros() - 1, nrefs, refs); } void EventHandler::cancelDelete_cb(void *) { widgets->destroyDialog(); deleting_a_macro = false; } void EventHandler::delete_cb(void *) { if (macromgr->getNMacros() == 0) { // FIX THIS-- the button shouldn't even exist return; } if (creating_a_macro) { std::cout << "Sorry, can't delete a macro while defining a macro" << std::endl; return; } widgets->destroyDialog(); deleting_a_macro = true; creating_a_macro = false; // If there's only one macro, can delete it without popping up the // dialog. Do this by pretending the apply button was hit. if (macromgr->getNMacros() == 1) { applyMacro_cb(0); return; } static Widgets::ButtonData button( "Cancel", &EventHandler::cancelDelete_cb, 0); widgets->createDialog ("Click on the \"Apply\" button\nof the macro you wish to delete.", 1, &button); } void EventHandler::toggleFast_cb(void *) { fast_automoves = 1 - fast_automoves; if (widgets) { widgets->updateFastButton(fast_automoves); } } void EventHandler::newPuzzle_cb(void* arg) { if ((int)arg == preferences.getLength()) { reset_cb(0); return; } preferences.setLength((int)arg); int length = preferences.getLength(); polymgr->reset(length); puzzle_state->reset(length); history->reset(length); polymgr->getUntwistedFrame(&untwisted_frame); if (length > 3) { std::cerr << "WARNING: some of the twists look like garbage (sorry)." << std::endl; } machine->drawFrame(&untwisted_frame); } void EventHandler::keyPress_handler(Event* event, void *) { static char buf[10]; /* to store some previous keystrokes */ static int nchars_saved = 0; struct stickerspec sticker; int direction; int slicesmask; bool forward_first = false; buf[nchars_saved] = event->key; switch (buf[0]) { case 'h': case '?': /* help */ std::cout << "Typing any of the following keys in the window will do " << "something:" << std::endl << "\th or ? -- print this message" << std::endl << "\tc -- clear (reset to unscrambled state)" << std::endl << "\ts or C -- solve (cheat)" << std::endl << "\tS -- scramble %d moves" << std::endl << "\tu -- undo a move" << std::endl << "\tr -- redo a move" << std::endl << "\to -- toggle outlines" << std::endl << "\tw -- turn background white" << std::endl << "\tb -- turn background black again because the white " << "hurt your eyes" << std::endl << "\tf -- toggle fast automoves mode" << std::endl << "\t1-9 -- act as modifier keys controlling slice " << "mask for current twist" << std::endl << "\t0 -- clear slice mask for next twist" << std::endl << "\tshift-1-9 -- scramble by that many moves" << std::endl << "\tm followed by any key -- mark a position (like in vi)" << std::endl << "\t' or ` followed by any key -- go to mark (like in vi)" << std::endl << "\tx -- twirl to the right" << std::endl << "\tX -- twirl to the left" << std::endl << "\ty -- tilt forward" << std::endl << "\tY -- tilt backward" << std::endl << "\tCTRL-s -- save" << std::endl << "\tCTRL-p -- print (generate PostScript file)" << std::endl << "\tCTRL-d -- dump state to stdout (for debugging)" << std::endl << "\tCTRL-r -- read state from stdin (for debugging)" << std::endl << "\tq -- quit" << std::endl; break; case 'm': /* mark-- get another keypress */ if (nchars_saved == 1) { nchars_saved = 0; char mark_ch = buf[1]; if ((mark_ch == MARK_MACRO_OPEN) || (mark_ch == MARK_MACRO_CLOSE) || (mark_ch == MARK_SCRAMBLE_BOUNDARY)) { printf("Can't create a mark with reserved character %c.\n", mark_ch); } history->mark((int)mark_ch); } else nchars_saved = 1; /* process it when we get another char */ break; case '\'': /* mark-- get another keypress */ forward_first = true; // fall through case '`': if (nchars_saved == 1) { int nmoves_made = 0; int mark = buf[1]; int status; nchars_saved = 0; while ((status = history->goTowardsMark(mark, &sticker, &direction, &slicesmask, forward_first)) == 1) { if (! fast_automoves) { showAnimation(&sticker, direction, slicesmask); } puzzle_state->applyMove(&sticker, direction, slicesmask); if (preferences.getBoolProperty(M4D_DRAW_NEW_STATE)) machine->drawFrame(&untwisted_frame); ++nmoves_made; } if (fast_automoves) { machine->drawFrame(&untwisted_frame); } if (status == -1) { printf("No mark '%c'(%d)!\n", mark, mark); break; } if (nmoves_made == 0 && status == 0) { /* already at the mark */ printf("Already at mark '%c'(%d)!\n", mark, mark); break; } } else nchars_saved = 1; /* process it when we get another char */ break; case 'u': /* undo */ undo_cb(0); break; case 'U': /* undo that goes past scramble boundary */ undo_cb((void*)1); break; case 'r': /* redo */ redo_cb(0); break; case 'R': /* redo that goes past scramble boundary */ redo_cb((void*)1); break; case 'C': /* cheat */ cheat_cb(0); break; case 'w': /* make background white */ machine->turnBackgroundWhite(); machine->drawFrame(&untwisted_frame); break; case 'b': /* make background black */ machine->turnBackgroundBlack(); machine->drawFrame(&untwisted_frame); break; case 'o': /* toggle outlines */ machine->toggleOutline(); machine->drawFrame(&untwisted_frame); break; case 'f': toggleFast_cb(0); break; case 'c': /* clear (reset) */ reset_cb(0); break; case 'q': /* quit */ quit_cb(0); break; case 24: // XXX widgets->debuggingHack(macromgr); break; // 1 through 9 ors slices mask of next button event case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': next_slicesmask |= (1 << (event->key - '1')); break; // 0 clears next_slicesmask -- should not be needed anymore, // but leave it just in case. case '0': next_slicesmask = 0; break; case '!': scramble_cb((void *)1); break; case '@': scramble_cb((void *)2); break; case '#': scramble_cb((void *)3); break; case '$': scramble_cb((void *)4); break; case '%': scramble_cb((void *)5); break; case '^': scramble_cb((void *)6); break; case '&': scramble_cb((void *)7); break; case '*': scramble_cb((void *)8); break; case '(': scramble_cb((void *)9); break; case 's': // "solve", which now means cheat cheat_cb(0); break; case 'S': // full scramble scramble_cb((void *)nscramblechen); break; /* * Simulate mouse motion since it wasn't implemented yet. * Actually maybe this is a convenient way of altering tilt and twirl. */ case 'x': /* twirl 1 degree to the right */ { real inc = preferences.getRealProperty(M4D_INC, DEF_TWIST_INCREMENT); polymgr->incTwirl(DTOR(inc)); polymgr->getUntwistedFrame(&untwisted_frame); machine->drawFrame(&untwisted_frame); } break; case 'X': /* twirl 1 degree to the left */ { real inc = preferences.getRealProperty(M4D_INC, DEF_TWIST_INCREMENT); polymgr->incTwirl(DTOR(-inc)); polymgr->getUntwistedFrame(&untwisted_frame); machine->drawFrame(&untwisted_frame); } break; case 'y': /* tilt 1 degree forward */ { real inc = preferences.getRealProperty(M4D_INC, DEF_TWIST_INCREMENT); polymgr->incTilt(DTOR(inc)); polymgr->getUntwistedFrame(&untwisted_frame); machine->drawFrame(&untwisted_frame); } break; case 'Y': /* tilt 1 degree backward */ { real inc = preferences.getRealProperty(M4D_INC, DEF_TWIST_INCREMENT); polymgr->incTilt(DTOR(-inc)); polymgr->getUntwistedFrame(&untwisted_frame); machine->drawFrame(&untwisted_frame); } break; case 'S' - 'A' + 1: /* save */ save_cb(0); break; case 'P' - 'A' + 1: /* print */ print_cb(0); break; case 'D' - 'A' + 1: /* testing state dump */ printf("\n"); puzzle_state->dump(stdout); break; case 'R' - 'A' + 1: /* testing state read */ printf("Enter state:\n"); if (puzzle_state->read(stdin)) { machine->drawFrame(&untwisted_frame); // FIX THIS--- currently no way to put the moves of a // scramble into history history->clear(); } else std::cerr << "Couldn't read the state." << std::endl; break; default: printf("Got key '%c'(%d)\n", event->key, event->key); } } void EventHandler::keyRelease_handler(Event* event, void *) { switch (event->key) { // 1 through 9 ors slices mask of next button event case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': next_slicesmask &= ~(1 << (event->key - '1')); break; default: break; } } void EventHandler::drag_handler(Event *event, void *) { static real inc; if (inc == 0) inc = preferences.getRealProperty(M4D_INC, DEF_TWIST_INCREMENT); if (dragging && event->button == MIDDLEBUTTON && (event->dx || event->dy)) { polymgr->incTilt(DTOR(event->dy * inc)); polymgr->incTwirl(DTOR(event->dx * inc)); polymgr->getUntwistedFrame(&untwisted_frame); machine->drawFrame(&untwisted_frame); } } #if 0 void EventHandler::dumpSticker(struct stickerspec *sticker) { printf("\t"); PRVEC4(%d, sticker->coords); printf("\t"); PRINT(sticker->face); printf("\t"); PRINT(sticker->dim); printf("\t"); PRINT(sticker->id_within_cube); printf("\t"); PRINT(sticker->id_within_face); } #endif // Local Variables: // c-basic-offset: 4 // c-comment-only-line-offset: 0 // c-file-offsets: ((defun-block-intro . +) (block-open . 0) (substatement-open . 0) (statement-cont . +) (statement-case-open . +4) (arglist-intro . +) (arglist-close . +) (inline-open . 0)) // indent-tabs-mode: nil // End: