Subversion Repositories Kolibri OS

Rev

Blame | Last modification | View Log | RSS feed

  1. /*
  2.  * OpenTyrian: A modern cross-platform port of Tyrian
  3.  * Copyright (C) 2007-2009  The OpenTyrian Development Team
  4.  *
  5.  * This program is free software; you can redistribute it and/or
  6.  * modify it under the terms of the GNU General Public License
  7.  * as published by the Free Software Foundation; either version 2
  8.  * of the License, or (at your option) any later version.
  9.  *
  10.  * This program is distributed in the hope that it will be useful,
  11.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13.  * GNU General Public License for more details.
  14.  *
  15.  * You should have received a copy of the GNU General Public License
  16.  * along with this program; if not, write to the Free Software
  17.  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  18.  */
  19. #include "joystick.h"
  20.  
  21. #include "config.h"
  22. #include "config_file.h"
  23. #include "file.h"
  24. #include "keyboard.h"
  25. #include "nortsong.h"
  26. #include "opentyr.h"
  27. #include "params.h"
  28. #include "varz.h"
  29. #include "video.h"
  30.  
  31. #include <assert.h>
  32. #include <ctype.h>
  33. #include <string.h>
  34.  
  35. int joystick_axis_threshold( int j, int value );
  36. int check_assigned( SDL_Joystick *joystick_handle, const Joystick_assignment assignment[2] );
  37.  
  38. const char *assignment_to_code( const Joystick_assignment *assignment );
  39. void code_to_assignment( Joystick_assignment *assignment, const char *buffer );
  40.  
  41. int joystick_repeat_delay = 300; // milliseconds, repeat delay for buttons
  42. bool joydown = false;            // any joystick buttons down, updated by poll_joysticks()
  43. bool ignore_joystick = false;
  44.  
  45. int joysticks = 0;
  46. Joystick *joystick = NULL;
  47.  
  48. static const int joystick_analog_max = 32767;
  49.  
  50. // eliminates axis movement below the threshold
  51. int joystick_axis_threshold( int j, int value )
  52. {
  53.         assert(j < joysticks);
  54.        
  55.         bool negative = value < 0;
  56.         if (negative)
  57.                 value = -value;
  58.        
  59.         if (value <= joystick[j].threshold * 1000)
  60.                 return 0;
  61.        
  62.         value -= joystick[j].threshold * 1000;
  63.        
  64.         return negative ? -value : value;
  65. }
  66.  
  67. // converts joystick axis to sane Tyrian-usable value (based on sensitivity)
  68. int joystick_axis_reduce( int j, int value )
  69. {
  70.         assert(j < joysticks);
  71.        
  72.         value = joystick_axis_threshold(j, value);
  73.        
  74.         if (value == 0)
  75.                 return 0;
  76.        
  77.         return value / (3000 - 200 * joystick[j].sensitivity);
  78. }
  79.  
  80. // converts analog joystick axes to an angle
  81. // returns false if axes are centered (there is no angle)
  82. bool joystick_analog_angle( int j, float *angle )
  83. {
  84.         assert(j < joysticks);
  85.        
  86.         float x = joystick_axis_threshold(j, joystick[j].x), y = joystick_axis_threshold(j, joystick[j].y);
  87.        
  88.         if (x != 0)
  89.         {
  90.                 *angle += atanf(-y / x);
  91.                 *angle += (x < 0) ? -M_PI_2 : M_PI_2;
  92.                 return true;
  93.         }
  94.         else if (y != 0)
  95.         {
  96.                 *angle += y < 0 ? M_PI : 0;
  97.                 return true;
  98.         }
  99.        
  100.         return false;
  101. }
  102.  
  103. /* gives back value 0..joystick_analog_max indicating that one of the assigned
  104.  * buttons has been pressed or that one of the assigned axes/hats has been moved
  105.  * in the assigned direction
  106.  */
  107. int check_assigned( SDL_Joystick *joystick_handle, const Joystick_assignment assignment[2] )
  108. {
  109.         int result = 0;
  110.        
  111.         for (int i = 0; i < 2; i++)
  112.         {
  113.                 int temp = 0;
  114.                
  115.                 switch (assignment[i].type)
  116.                 {
  117.                 case NONE:
  118.                         continue;
  119.                        
  120.                 case AXIS:
  121.                         temp = SDL_JoystickGetAxis(joystick_handle, assignment[i].num);
  122.                        
  123.                         if (assignment[i].negative_axis)
  124.                                 temp = -temp;
  125.                         break;
  126.                
  127.                 case BUTTON:
  128.                         temp = SDL_JoystickGetButton(joystick_handle, assignment[i].num) == 1 ? joystick_analog_max : 0;
  129.                         break;
  130.                
  131.                 case HAT:
  132.                         temp = SDL_JoystickGetHat(joystick_handle, assignment[i].num);
  133.                        
  134.                         if (assignment[i].x_axis)
  135.                                 temp &= SDL_HAT_LEFT | SDL_HAT_RIGHT;
  136.                         else
  137.                                 temp &= SDL_HAT_UP | SDL_HAT_DOWN;
  138.                        
  139.                         if (assignment[i].negative_axis)
  140.                                 temp &= SDL_HAT_LEFT | SDL_HAT_UP;
  141.                         else
  142.                                 temp &= SDL_HAT_RIGHT | SDL_HAT_DOWN;
  143.                        
  144.                         temp = temp ? joystick_analog_max : 0;
  145.                         break;
  146.                 }
  147.                
  148.                 if (temp > result)
  149.                         result = temp;
  150.         }
  151.        
  152.         return result;
  153. }
  154.  
  155. // updates joystick state
  156. void poll_joystick( int j )
  157. {
  158.         assert(j < joysticks);
  159.        
  160.         if (joystick[j].handle == NULL)
  161.                 return;
  162.        
  163.         SDL_JoystickUpdate();
  164.        
  165.         // indicates that a direction/action was pressed since last poll
  166.         joystick[j].input_pressed = false;
  167.        
  168.         // indicates that an direction/action has been held long enough to fake a repeat press
  169.         bool repeat = joystick[j].joystick_delay < SDL_GetTicks();
  170.        
  171.         // update direction state
  172.         for (uint d = 0; d < COUNTOF(joystick[j].direction); d++)
  173.         {
  174.                 bool old = joystick[j].direction[d];
  175.                
  176.                 joystick[j].analog_direction[d] = check_assigned(joystick[j].handle, joystick[j].assignment[d]);
  177.                 joystick[j].direction[d] = joystick[j].analog_direction[d] > (joystick_analog_max / 2);
  178.                 joydown |= joystick[j].direction[d];
  179.                
  180.                 joystick[j].direction_pressed[d] = joystick[j].direction[d] && (!old || repeat);
  181.                 joystick[j].input_pressed |= joystick[j].direction_pressed[d];
  182.         }
  183.        
  184.         joystick[j].x = -joystick[j].analog_direction[3] + joystick[j].analog_direction[1];
  185.         joystick[j].y = -joystick[j].analog_direction[0] + joystick[j].analog_direction[2];
  186.        
  187.         // update action state
  188.         for (uint d = 0; d < COUNTOF(joystick[j].action); d++)
  189.         {
  190.                 bool old = joystick[j].action[d];
  191.                
  192.                 joystick[j].action[d] = check_assigned(joystick[j].handle, joystick[j].assignment[d + COUNTOF(joystick[j].direction)]) > (joystick_analog_max / 2);
  193.                 joydown |= joystick[j].action[d];
  194.                
  195.                 joystick[j].action_pressed[d] = joystick[j].action[d] && (!old || repeat);
  196.                 joystick[j].input_pressed |= joystick[j].action_pressed[d];
  197.         }
  198.        
  199.         joystick[j].confirm = joystick[j].action[0] || joystick[j].action[4];
  200.         joystick[j].cancel = joystick[j].action[1] || joystick[j].action[5];
  201.        
  202.         // if new input, reset press-repeat delay
  203.         if (joystick[j].input_pressed)
  204.                 joystick[j].joystick_delay = SDL_GetTicks() + joystick_repeat_delay;
  205. }
  206.  
  207. // updates all joystick states
  208. void poll_joysticks( void )
  209. {
  210.         joydown = false;
  211.        
  212.         for (int j = 0; j < joysticks; j++)
  213.                 poll_joystick(j);
  214. }
  215.  
  216. // sends SDL KEYDOWN and KEYUP events for a key
  217. void push_key( SDLKey key )
  218. {
  219.         SDL_Event e;
  220.        
  221.         memset(&e.key.keysym, 0, sizeof(e.key.keysym));
  222.        
  223.         e.key.keysym.sym = key;
  224.         e.key.keysym.unicode = key;
  225.        
  226.         e.key.state = SDL_RELEASED;
  227.        
  228.         e.type = SDL_KEYDOWN;
  229.         SDL_PushEvent(&e);
  230.        
  231.         e.type = SDL_KEYUP;
  232.         SDL_PushEvent(&e);
  233. }
  234.  
  235. // helps us be lazy by pretending joysticks are a keyboard (useful for menus)
  236. void push_joysticks_as_keyboard( void )
  237. {
  238.         const SDLKey confirm = SDLK_RETURN, cancel = SDLK_ESCAPE;
  239.         const SDLKey direction[4] = { SDLK_UP, SDLK_RIGHT, SDLK_DOWN, SDLK_LEFT };
  240.        
  241.         poll_joysticks();
  242.        
  243.         for (int j = 0; j < joysticks; j++)
  244.         {
  245.                 if (!joystick[j].input_pressed)
  246.                         continue;
  247.                
  248.                 if (joystick[j].confirm)
  249.                         push_key(confirm);
  250.                 if (joystick[j].cancel)
  251.                         push_key(cancel);
  252.                
  253.                 for (uint d = 0; d < COUNTOF(joystick[j].direction_pressed); d++)
  254.                 {
  255.                         if (joystick[j].direction_pressed[d])
  256.                                 push_key(direction[d]);
  257.                 }
  258.         }
  259. }
  260.  
  261. // initializes SDL joystick system and loads assignments for joysticks found
  262. void init_joysticks( void )
  263. {
  264.         if (ignore_joystick)
  265.                 return;
  266.        
  267.         if (SDL_InitSubSystem(SDL_INIT_JOYSTICK))
  268.         {
  269.                 fprintf(stderr, "warning: failed to initialize joystick system: %s\n", SDL_GetError());
  270.                 ignore_joystick = true;
  271.                 return;
  272.         }
  273.        
  274.         SDL_JoystickEventState(SDL_IGNORE);
  275.        
  276.         joysticks = SDL_NumJoysticks();
  277.         joystick = malloc(joysticks * sizeof(*joystick));
  278.        
  279.         for (int j = 0; j < joysticks; j++)
  280.         {
  281.                 memset(&joystick[j], 0, sizeof(*joystick));
  282.                
  283.                 joystick[j].handle = SDL_JoystickOpen(j);
  284.                 if (joystick[j].handle != NULL)
  285.                 {
  286.                         printf("joystick detected: %s ", SDL_JoystickName(j));
  287.                         printf("(%d axes, %d buttons, %d hats)\n",
  288.                                SDL_JoystickNumAxes(joystick[j].handle),
  289.                                SDL_JoystickNumButtons(joystick[j].handle),
  290.                                SDL_JoystickNumHats(joystick[j].handle));
  291.                        
  292.                         if (!load_joystick_assignments(&opentyrian_config, j))
  293.                                 reset_joystick_assignments(j);
  294.                 }
  295.         }
  296.        
  297.         if (joysticks == 0)
  298.                 printf("no joysticks detected\n");
  299. }
  300.  
  301. // deinitializes SDL joystick system and saves joystick assignments
  302. void deinit_joysticks( void )
  303. {
  304.         if (ignore_joystick)
  305.                 return;
  306.        
  307.         for (int j = 0; j < joysticks; j++)
  308.         {
  309.                 if (joystick[j].handle != NULL)
  310.                 {
  311.                         save_joystick_assignments(&opentyrian_config, j);
  312.                         SDL_JoystickClose(joystick[j].handle);
  313.                 }
  314.         }
  315.        
  316.         free(joystick);
  317.        
  318.         SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
  319. }
  320.  
  321. void reset_joystick_assignments( int j )
  322. {
  323.         assert(j < joysticks);
  324.        
  325.         // defaults: first 2 axes, first hat, first 6 buttons
  326.         for (uint a = 0; a < COUNTOF(joystick[j].assignment); a++)
  327.         {
  328.                 // clear assignments
  329.                 for (uint i = 0; i < COUNTOF(joystick[j].assignment[a]); i++)
  330.                         joystick[j].assignment[a][i].type = NONE;
  331.                
  332.                 if (a < 4)
  333.                 {
  334.                         if (SDL_JoystickNumAxes(joystick[j].handle) >= 2)
  335.                         {
  336.                                 joystick[j].assignment[a][0].type = AXIS;
  337.                                 joystick[j].assignment[a][0].num = (a + 1) % 2;
  338.                                 joystick[j].assignment[a][0].negative_axis = (a == 0 || a == 3);
  339.                         }
  340.                        
  341.                         if (SDL_JoystickNumHats(joystick[j].handle) >= 1)
  342.                         {
  343.                                 joystick[j].assignment[a][1].type = HAT;
  344.                                 joystick[j].assignment[a][1].num = 0;
  345.                                 joystick[j].assignment[a][1].x_axis = (a == 1 || a == 3);
  346.                                 joystick[j].assignment[a][1].negative_axis = (a == 0 || a == 3);
  347.                         }
  348.                 }
  349.                 else
  350.                 {
  351.                         if (a - 4 < (unsigned)SDL_JoystickNumButtons(joystick[j].handle))
  352.                         {
  353.                                 joystick[j].assignment[a][0].type = BUTTON;
  354.                                 joystick[j].assignment[a][0].num = a - 4;
  355.                         }
  356.                 }
  357.         }
  358.        
  359.         joystick[j].analog = false;
  360.         joystick[j].sensitivity = 5;
  361.         joystick[j].threshold = 5;
  362. }
  363.  
  364. static const char* const assignment_names[] =
  365. {
  366.         "up",
  367.         "right",
  368.         "down",
  369.         "left",
  370.         "fire",
  371.         "change fire",
  372.         "left sidekick",
  373.         "right sidekick",
  374.         "menu",
  375.         "pause",
  376. };
  377.  
  378. bool load_joystick_assignments( Config *config, int j )
  379. {
  380.         ConfigSection *section = config_find_section(config, "joystick", SDL_JoystickName(j));
  381.         if (section == NULL)
  382.                 return false;
  383.        
  384.         if (!config_get_bool_option(section, "analog", &joystick[j].analog))
  385.                 joystick[j].analog = false;
  386.        
  387.         joystick[j].sensitivity = config_get_or_set_int_option(section, "sensitivity", 5);
  388.  
  389.         joystick[j].threshold = config_get_or_set_int_option(section, "threshold", 5);
  390.        
  391.         for (size_t a = 0; a < COUNTOF(assignment_names); ++a)
  392.         {
  393.                 for (unsigned int i = 0; i < COUNTOF(joystick[j].assignment[a]); ++i)
  394.                         joystick[j].assignment[a][i].type = NONE;
  395.                
  396.                 ConfigOption *option = config_get_option(section, assignment_names[a]);
  397.                 if (option == NULL)
  398.                         continue;
  399.                
  400.                 foreach_option_i_value(i, value, option)
  401.                 {
  402.                         if (i >= COUNTOF(joystick[j].assignment[a]))
  403.                                 break;
  404.                        
  405.                         code_to_assignment(&joystick[j].assignment[a][i], value);
  406.                 }
  407.         }
  408.        
  409.         return true;
  410. }
  411.  
  412. bool save_joystick_assignments( Config *config, int j )
  413. {
  414.         ConfigSection *section = config_find_or_add_section(config, "joystick", SDL_JoystickName(j));
  415.         if (section == NULL)
  416.                 exit(EXIT_FAILURE);  // out of memory
  417.        
  418.         config_set_bool_option(section, "analog", joystick[j].analog, NO_YES);
  419.        
  420.         config_set_int_option(section, "sensitivity", joystick[j].sensitivity);
  421.        
  422.         config_set_int_option(section, "threshold", joystick[j].threshold);
  423.        
  424.         for (size_t a = 0; a < COUNTOF(assignment_names); ++a)
  425.         {
  426.                 ConfigOption *option = config_set_option(section, assignment_names[a], NULL);
  427.                 if (option == NULL)
  428.                         exit(EXIT_FAILURE);  // out of memory
  429.                
  430.                 option = config_set_value(option, NULL);
  431.                 if (option == NULL)
  432.                         exit(EXIT_FAILURE);  // out of memory
  433.  
  434.                 for (size_t i = 0; i < COUNTOF(joystick[j].assignment[a]); ++i)
  435.                 {
  436.                         if (joystick[j].assignment[a][i].type == NONE)
  437.                                 continue;
  438.                        
  439.                         option = config_add_value(option, assignment_to_code(&joystick[j].assignment[a][i]));
  440.                         if (option == NULL)
  441.                                 exit(EXIT_FAILURE);  // out of memory
  442.                 }
  443.         }
  444.        
  445.         return true;
  446. }
  447.  
  448. // fills buffer with comma separated list of assigned joystick functions
  449. void joystick_assignments_to_string( char *buffer, size_t buffer_len, const Joystick_assignment *assignments )
  450. {
  451.         strncpy(buffer, "", buffer_len);
  452.        
  453.         bool comma = false;
  454.         for (uint i = 0; i < COUNTOF(*joystick->assignment); ++i)
  455.         {
  456.                 if (assignments[i].type == NONE)
  457.                         continue;
  458.                
  459.                 size_t len = snprintf(buffer, buffer_len, "%s%s",
  460.                                       comma ? ", " : "",
  461.                                       assignment_to_code(&assignments[i]));
  462.                 buffer += len;
  463.                 buffer_len -= len;
  464.                
  465.                 comma = true;
  466.         }
  467. }
  468.  
  469. // reverse of assignment_to_code()
  470. void code_to_assignment( Joystick_assignment *assignment, const char *buffer )
  471. {
  472.         memset(assignment, 0, sizeof(*assignment));
  473.        
  474.         char axis = 0, direction = 0;
  475.        
  476.         if (sscanf(buffer, " AX %d%c", &assignment->num, &direction) == 2)
  477.                 assignment->type = AXIS;
  478.         else if (sscanf(buffer, " BTN %d", &assignment->num) == 1)
  479.                 assignment->type = BUTTON;
  480.         else if (sscanf(buffer, " H %d%c%c", &assignment->num, &axis, &direction) == 3)
  481.                 assignment->type = HAT;
  482.        
  483.         if (assignment->num == 0)
  484.                 assignment->type = NONE;
  485.         else
  486.                 --assignment->num;
  487.        
  488.         assignment->x_axis = (toupper(axis) == 'X');
  489.         assignment->negative_axis = (toupper(direction) == '-');
  490. }
  491.  
  492. /* gives the short (6 or less characters) identifier for a joystick assignment
  493.  *
  494.  * two of these per direction/action is all that can fit on the joystick config screen,
  495.  * assuming two digits for the axis/button/hat number
  496.  */
  497. const char *assignment_to_code( const Joystick_assignment *assignment )
  498. {
  499.         static char name[7];
  500.        
  501.         switch (assignment->type)
  502.         {
  503.         case NONE:
  504.                 strcpy(name, "");
  505.                 break;
  506.                
  507.         case AXIS:
  508.                 snprintf(name, sizeof(name), "AX %d%c",
  509.                          assignment->num + 1,
  510.                          assignment->negative_axis ? '-' : '+');
  511.                 break;
  512.                
  513.         case BUTTON:
  514.                 snprintf(name, sizeof(name), "BTN %d",
  515.                          assignment->num + 1);
  516.                 break;
  517.                
  518.         case HAT:
  519.                 snprintf(name, sizeof(name), "H %d%c%c",
  520.                          assignment->num + 1,
  521.                          assignment->x_axis ? 'X' : 'Y',
  522.                          assignment->negative_axis ? '-' : '+');
  523.                 break;
  524.         }
  525.        
  526.         return name;
  527. }
  528.  
  529. // captures joystick input for configuring assignments
  530. // returns false if non-joystick input was detected
  531. // TODO: input from joystick other than the one being configured probably should not be ignored
  532. bool detect_joystick_assignment( int j, Joystick_assignment *assignment )
  533. {
  534.         // get initial joystick state to compare against to see if anything was pressed
  535.        
  536.         const int axes = SDL_JoystickNumAxes(joystick[j].handle);
  537.         Sint16 *axis = malloc(axes * sizeof(*axis));
  538.         for (int i = 0; i < axes; i++)
  539.                 axis[i] = SDL_JoystickGetAxis(joystick[j].handle, i);
  540.        
  541.         const int buttons = SDL_JoystickNumButtons(joystick[j].handle);
  542.         Uint8 *button = malloc(buttons * sizeof(*button));
  543.         for (int i = 0; i < buttons; i++)
  544.                 button[i] = SDL_JoystickGetButton(joystick[j].handle, i);
  545.        
  546.         const int hats = SDL_JoystickNumHats(joystick[j].handle);
  547.         Uint8 *hat = malloc(hats * sizeof(*hat));
  548.         for (int i = 0; i < hats; i++)
  549.                 hat[i] = SDL_JoystickGetHat(joystick[j].handle, i);
  550.        
  551.         bool detected = false;
  552.        
  553.         do
  554.         {
  555.                 setjasondelay(1);
  556.                
  557.                 SDL_JoystickUpdate();
  558.                
  559.                 for (int i = 0; i < axes; ++i)
  560.                 {
  561.                         Sint16 temp = SDL_JoystickGetAxis(joystick[j].handle, i);
  562.                        
  563.                         if (abs(temp - axis[i]) > joystick_analog_max * 2 / 3)
  564.                         {
  565.                                 assignment->type = AXIS;
  566.                                 assignment->num = i;
  567.                                 assignment->negative_axis = temp < axis[i];
  568.                                 detected = true;
  569.                                 break;
  570.                         }
  571.                 }
  572.                
  573.                 for (int i = 0; i < buttons; ++i)
  574.                 {
  575.                         Uint8 new_button = SDL_JoystickGetButton(joystick[j].handle, i),
  576.                               changed = button[i] ^ new_button;
  577.                        
  578.                         if (!changed)
  579.                                 continue;
  580.                        
  581.                         if (new_button == 0) // button was released
  582.                         {
  583.                                 button[i] = new_button;
  584.                         }
  585.                         else                 // button was pressed
  586.                         {
  587.                                 assignment->type = BUTTON;
  588.                                 assignment->num = i;
  589.                                 detected = true;
  590.                                 break;
  591.                         }
  592.                 }
  593.                
  594.                 for (int i = 0; i < hats; ++i)
  595.                 {
  596.                         Uint8 new_hat = SDL_JoystickGetHat(joystick[j].handle, i),
  597.                               changed = hat[i] ^ new_hat;
  598.                        
  599.                         if (!changed)
  600.                                 continue;
  601.                        
  602.                         if ((new_hat & changed) == SDL_HAT_CENTERED) // hat was centered
  603.                         {
  604.                                 hat[i] = new_hat;
  605.                         }
  606.                         else
  607.                         {
  608.                                 assignment->type = HAT;
  609.                                 assignment->num = i;
  610.                                 assignment->x_axis = changed & (SDL_HAT_LEFT | SDL_HAT_RIGHT);
  611.                                 assignment->negative_axis = changed & (SDL_HAT_LEFT | SDL_HAT_UP);
  612.                                 detected = true;
  613.                         }
  614.                 }
  615.                
  616.                 service_SDL_events(true);
  617.                 JE_showVGA();
  618.                
  619.                 wait_delay();
  620.         }
  621.         while (!detected && !newkey && !newmouse);
  622.        
  623.         free(axis);
  624.         free(button);
  625.         free(hat);
  626.        
  627.         return detected;
  628. }
  629.  
  630. // compares relevant parts of joystick assignments for equality
  631. bool joystick_assignment_cmp( const Joystick_assignment *a, const Joystick_assignment *b )
  632. {
  633.         if (a->type == b->type)
  634.         {
  635.                 switch (a->type)
  636.                 {
  637.                 case NONE:
  638.                         return true;
  639.                 case AXIS:
  640.                         return (a->num == b->num) &&
  641.                                (a->negative_axis == b->negative_axis);
  642.                 case BUTTON:
  643.                         return (a->num == b->num);
  644.                 case HAT:
  645.                         return (a->num == b->num) &&
  646.                                (a->x_axis == b->x_axis) &&
  647.                                (a->negative_axis == b->negative_axis);
  648.                 }
  649.         }
  650.        
  651.         return false;
  652. }
  653.  
  654.