Complete.Org: Mailing Lists: Archives: freeciv-dev: March 2003:
[Freeciv-Dev] Re: (PR#3727) Rectangular selection with right-click-and-d
Home

[Freeciv-Dev] Re: (PR#3727) Rectangular selection with right-click-and-d

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: a-l@xxxxxxx
Subject: [Freeciv-Dev] Re: (PR#3727) Rectangular selection with right-click-and-drag
From: "Mike Kaufman" <kaufman@xxxxxxxxxxxxxxxxxxxxxx>
Date: Thu, 20 Mar 2003 06:26:40 -0800
Reply-to: rt@xxxxxxxxxxxxxx

On Wed, Mar 19, 2003 at 12:14:23AM -0800, Jason Short wrote:
> > +**************************************************************************/
> > +void draw_rectangle(int src_x, int src_y, int dest_x, int dest_y,
> > +                    bool draw)
> > +{
> > +  int x1, y1, x2, y2;
> > +  int dist_x, dist_y;
> > +  int i, test_x;
> > +
> > +  dist_x = real_map_distance(src_x, src_y, dest_x, src_y);
> > +  dist_y = real_map_distance(src_x, src_y, src_x, dest_y);
> > +
> > +  test_x = src_x + dist_x;
> > +  normalize_map_pos(&test_x, &src_y);
> 
> Any time you call normalize_map_pos you need to check the result.  But 
> I'm not quite sure what the correct behavior should be in this case if 
> it returns FALSE.  Is this impossible (if so, you should assert(0))?  Or 
> is some other handling needed?
> 
> > +  if (test_x == dest_x)  {        /* is dest to the right of src? */
> > +    x1 = src_x;     x2 = dest_x;
> > +  } else  {
> > +    x1 = dest_x;    x2 = src_x;
> > +  }
> > +  if (dest_y >= src_y) {          /* vertical is simple */
> > +    y1 = src_y;     y2 = dest_y;
> > +  } else  {
> > +    y1 = dest_y;    y2 = src_y;
> > +  }
> 
> This code assumes the current topology, although doing differently may 
> turn out to be very difficult.  What if the map wraps in the Y 
> direction?  What if it is a isometric map (i.e., what civ2/smac/civ3 use)?
 
...

> This particular code looks like it will immediately segfault if you drag 
> your rectangle off into "unreal" positions.  This is impossible now, but 
> please don't assume it.  Something like

attached is my solution for the civworld code. it should work for all
topologies [that we are considering].

-mike

/**********************************************************************
 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
***********************************************************************/
#include <gtk/gtk.h>
#include <config.h>

#include "fcintl.h"
#include "map.h"
#include "mem.h"
#include "game.h"

#include "tilespec.h"
#include "support.h"
#include "clipfunc.h"
#include "chatline.h"
#include "maptools.h"
#include "mapview.h"
#include "nation_manage.h"
#include "startpos.h"
#include "tools.h"
#include "undo.h"
#include "toolfunc.h"
#include "attributesdlg.h"

#include "citytools.h"
#include "defaults.h"
#include "ed_unittools.h"
#include "ed_citytools.h"
#include "cw_mapview.h"
#include "maphand.h"

extern GdkGC *rubberband_line_gc;
extern GdkGC *civ_gc;

extern GtkWidget *map_canvas;

extern int map_view_x0;
extern int map_view_y0;

extern int draw_private_map;

/* coords in the internal map for drawing rectangles. */
static int first_x = -1, first_y = -1;
static int last_x = -1, last_y = -1;
static int single_paint = 1;

int undo_is_envoked = 0;

/**************************************************************************
...
**************************************************************************/
void sanity_check_specials(int x, int y, enum tile_special_type new_special)
{
  struct tile *ptile = map_get_tile(x, y);
  int terrain = ptile->terrain;
  struct tile_type *type = get_tile_type(terrain);

  if (terrain == T_OCEAN) {
    map_clear_special(x, y, S_ROAD);
    map_clear_special(x, y, S_IRRIGATION);
    map_clear_special(x, y, S_RAILROAD);
    map_clear_special(x, y, S_MINE);
    map_clear_special(x, y, S_POLLUTION);
    map_clear_special(x, y, S_HUT);
    map_clear_special(x, y, S_FORTRESS);
    map_clear_special(x, y, S_RIVER);
    map_clear_special(x, y, S_FARMLAND);
    map_clear_special(x, y, S_AIRBASE);
    map_clear_special(x, y, S_FALLOUT);
  }

  if (ptile->special & S_RAILROAD)
    map_set_special(x, y, S_ROAD);

  if (ptile->special & S_MINE
      && terrain != type->mining_result) {
    map_clear_special(x, y, S_MINE);
  }

  if (ptile->special & S_FARMLAND) {
    map_set_special(x, y, S_IRRIGATION);
  }

  if ((ptile->special & S_IRRIGATION || ptile->special & S_FARMLAND)
      && terrain != type->irrigation_result) {
    map_clear_special(x, y, S_IRRIGATION);
    map_clear_special(x, y, S_FARMLAND);
  }

  if (ptile->special & S_MINE && ptile->special & S_IRRIGATION) {
    if (new_special == S_MINE) {
      map_clear_special(x, y, S_IRRIGATION);
      map_clear_special(x, y, S_FARMLAND);
    } else {
      map_clear_special(x, y, S_MINE);
    }
  }

  if (ptile->special & S_SPECIAL_1 && ptile->special & S_SPECIAL_2) {
    if (new_special == S_SPECIAL_1) {
      map_clear_special(x, y, S_SPECIAL_2);
    } else {
      map_clear_special(x, y, S_SPECIAL_1);
    }
  }
}

/*****************************************************************************
 directly accessing the player_tile->known and ->seen is more efficient
 than going through fog/unfog_area. I think that the maphand.c stuff is in
 some way broken. You do not get the behavior you're looking for, mainly 
 because I think that ->seen shouldn't be unsigned... The only thing that 
 isn't taken care of is shared vision, but since it ain't implemented here yet,
 we don't care...
*****************************************************************************/
static void paint_tile(int x, int y)
{
  struct tile *ptile = map_get_tile(x, y);

  switch (selected_paint_type) {
  case PAINT_TERRAIN:
    ptile->terrain = selected_terrain;
    sanity_check_specials(x, y, selected_special);
    break;
  case PAINT_SPECIAL:
    if (selected_special == S_NO_SPECIAL)
      ptile->special = 0;
    else
      ptile->special |= selected_special;
    sanity_check_specials(x, y, selected_special);
    break;
  case PAINT_FOG:
    if (game.nplayers > 0) {
      if (selected_known == TILE_UNKNOWN) {
          forget_tile(game.player_ptr, x, y);
      } else if(selected_known == TILE_KNOWN_FOGGED) {
        struct city *pcity;
 
        if(!map_get_known(x, y, game.player_ptr)) {
          map_set_known(x, y, game.player_ptr);
          /* if it's unknown, it really should not be seen to begin with */
          map_get_player_tile(x, y, game.player_ptr)->seen = 0;
        } else if (map_get_player_tile(x, y, game.player_ptr)->seen != 0) {
            map_change_seen(x, y, game.player_ptr, -1);
        }

        reality_check_city(game.player_ptr, x, y);

        if ((pcity = map_get_city(x, y))) {
          update_dumb_city(game.player_ptr, pcity);
        }

        map_city_radius_iterate(x, y, x1, y1) {
          pcity = map_get_city(x1, y1);
          if (pcity && city_owner(pcity) == game.player_ptr) {
            update_city_tile_status_map(pcity, x, y);
          }
        } map_city_radius_iterate_end;
        sync_cities();
      } else if(selected_known == TILE_KNOWN) {
        struct city *pcity;

        map_set_known(x, y, game.player_ptr);
        map_change_seen(x, y, game.player_ptr, 1);

        reality_check_city(game.player_ptr, x, y);

        if ((pcity = map_get_city(x, y))) {
          update_dumb_city(game.player_ptr, pcity);
        }

        map_city_radius_iterate(x, y, x1, y1) {
          pcity = map_get_city(x1, y1);
          if (pcity && city_owner(pcity) == game.player_ptr) {
            update_city_tile_status_map(pcity, x, y);
          }
        } map_city_radius_iterate_end;
        sync_cities();
      }
    } else {
      append_output_window(_("You must create a nation "
                             "before using fogging tools."));
    }
    break;
  case PAINT_START:
    put_cross_overlay_tile(x, y);
    break;
  case PAINT_CITY:
    break;
  case PAINT_UNIT:
    break;
  case PAINT_DELETE:
    break;
  case PAINT_NONE:
    /* nothing */
    break;
  }
  if(single_paint){
    square_iterate(x, y, 1, x_itr, y_itr)
      refresh_tile_mapcanvas(x_itr, y_itr, 1);
    square_iterate_end;
  }
}

/*****************************************************************************
change the color, i.e. terrain, of the tile at (x, y)
*****************************************************************************/
void start_tile(gint x, gint y)
{
  if (!undo_is_envoked) {
    create_new_undo_node();
    undo_is_envoked = 1;
  }
  undo_add_tile(x, y);
  paint_tile(x, y);

  refresh_overview_viewrect();
}

/*****************************************************************************
...
*****************************************************************************/
void end_tile(gint x, gint y)
{
  undo_is_envoked = 0;
}

/*****************************************************************************
...
*****************************************************************************/
static void draw_bounding_rectangle()
{
  if(is_isometric){
    int Nx, Ny, Sx, Sy, Wx, Wy, Ex, Ey;

    get_canvas_xy(first_x, first_y, &Nx, &Ny); /* not necessarily N & S at */
    get_canvas_xy(last_x, last_y, &Sx, &Sy);   /* this point */

    if (last_x >= first_x && last_y >= first_y) { /* normal */
      get_canvas_xy(first_x, last_y, &Wx, &Wy);
      get_canvas_xy(last_x, first_y, &Ex, &Ey);
    } else if(last_x <= first_x && last_y <= first_y) { /* switch N & S */
      Ex = Nx; Ey = Ny;
      Nx = Sx; Ny = Sy;
      Sx = Ex; Sy = Ey;
      get_canvas_xy(first_x, last_y, &Ex, &Ey);
      get_canvas_xy(last_x, first_y, &Wx, &Wy);
    } else if(last_x < first_x) { /* start must be eastmost */
      Ex = Nx; Ey = Ny;
      Wx = Sx; Wy = Sy;
      get_canvas_xy(first_x, last_y, &Sx, &Sy);
      get_canvas_xy(last_x, first_y, &Nx, &Ny);
    } else { /* start must be westmost */
      Ex = Sx; Ey = Sy;
      Wx = Nx; Wy = Ny;
      get_canvas_xy(first_x, last_y, &Nx, &Ny);
      get_canvas_xy(last_x, first_y, &Sx, &Sy);
    }

    Nx += NORMAL_TILE_WIDTH / 2;
    Sx += NORMAL_TILE_WIDTH / 2;
    Sy += NORMAL_TILE_HEIGHT;
    Wy += NORMAL_TILE_HEIGHT / 2;
    Ex += NORMAL_TILE_WIDTH;
    Ey += NORMAL_TILE_HEIGHT / 2;

    gdk_draw_line(GTK_WIDGET(map_canvas)->window, rubberband_line_gc, 
                  Nx, Ny, Ex, Ey);
    gdk_draw_line(GTK_WIDGET(map_canvas)->window, rubberband_line_gc, 
                  Ex, Ey, Sx, Sy);
    gdk_draw_line(GTK_WIDGET(map_canvas)->window, rubberband_line_gc, 
                  Wx, Wy, Nx, Ny);
    gdk_draw_line(GTK_WIDGET(map_canvas)->window, rubberband_line_gc, 
                  Sx, Sy, Wx, Wy);
  }else{
    int x1, y1, x2, y2; /* the 1's are the start tile, 2's are the end tile */

    get_canvas_xy(first_x, first_y, &x1, &y1);
    get_canvas_xy(last_x, last_y, &x2, &y2); 

    if(x1 < x2)
      x2 += NORMAL_TILE_WIDTH - 1;
    else
      x1 += NORMAL_TILE_WIDTH - 1;
    if(y1 < y2)
      y2 += NORMAL_TILE_HEIGHT - 1;
    else
      y1 += NORMAL_TILE_HEIGHT - 1;

    /* draw new rectangle */
    gdk_draw_line(GTK_WIDGET(map_canvas)->window, rubberband_line_gc,
                  x1, y1, x1, y2);
    gdk_draw_line(GTK_WIDGET(map_canvas)->window, rubberband_line_gc,
                  x1, y1, x2, y1);
    gdk_draw_line(GTK_WIDGET(map_canvas)->window, rubberband_line_gc,
                  x2, y2, x2, y1);
    gdk_draw_line(GTK_WIDGET(map_canvas)->window, rubberband_line_gc,
                  x2, y2, x1, y2);
  }
}

/**************************************************************************
...
**************************************************************************/
static void undraw_rect(int init)
{
  if (first_x == -1)
    return;

  draw_bounding_rectangle();

  if (init) {
    first_x = -1, first_y = -1;
    last_x = -1, last_y = -1;
  }
}

/**************************************************************************
...
**************************************************************************/
static void draw_rect(void)
{
  draw_bounding_rectangle();
}

/*****************************************************************************
start drawing of rectangle: save start tile, draw rubber band rectangle

we cannot assume that input will be continous in the set of whole numbers.
the mouse movement might skip a couple of numbers. The assumption we make is
that the mouse won't skip 5 tiles without actually travelling across 
the boundary.
*****************************************************************************/
void start_rect(gint x, gint y)
{
  static int old_x;
  static int old_gradient = 1;
  int f1, f2, u, v;
  int gradient;

  undraw_rect(FALSE);

  if (first_x == -1) {
    first_x = last_x = old_x = x;
    first_y = y;
  }

  get_canvas_xy(first_x, first_y, &f1, &f2);
  get_canvas_xy(x, y, &u, &v);
 
  if(is_isometric){
    gradient = NORMAL_TILE_WIDTH/NORMAL_TILE_HEIGHT * (v - f2) - f1 + u;
  }else{
    gradient = u - f1;
  }
  if((old_gradient < 0 && gradient > 0 && gradient > 10 * NORMAL_TILE_WIDTH) 
     || (old_gradient > 0 && gradient < 0 
         && old_gradient - gradient > 5 * NORMAL_TILE_WIDTH))
    gradient = old_gradient;

  if(gradient > 0 && x < first_x)
    last_x = x + map.xsize;
  else if(gradient < 0 && x > first_x)
    last_x = x - map.xsize;
  else
    last_x = x;

  old_gradient = gradient;

  last_y = y;
  draw_rect();
}

/*****************************************************************************
undraw the rectangle, update the map stats, clear the start tile coords
the parameters x,y are not currently used.
*****************************************************************************/
void end_rect(gint x, gint y)
{
  int i, j;
  int start_x, start_y, end_x, end_y;
  int w, h;

  undraw_rect(FALSE);

  get_canvas_xy(first_x, first_y, &start_x, &start_y);
  get_canvas_xy(last_x, last_y, &end_x, &end_y);

  if (is_isometric) {
    if (first_x > last_x) {
      int tmp = first_x;
      first_x = last_x;
      last_x = tmp;
    }
    if (first_y > last_y) {
      int tmp = first_y;
      first_y = last_y;
      last_y = tmp;
    }
  } else {
    if (start_x > end_x) {
      int tmp = first_x;
      first_x = last_x;
      last_x = tmp;
    }
    if (start_y > end_y) {
      int tmp = first_y;
      first_y = last_y;
      last_y = tmp;
    }
  }
  create_new_undo_node();

  w = last_x - first_x;
  h = last_y - first_y;

  normalize_map_pos(&first_x, &first_y);
  last_x++; last_y++;
  normalize_map_pos(&last_x, &last_y);

  for(i = first_x; i != last_x; i++, normalize_map_pos(&i, &j)) {
    for(j = first_y; j != last_y; j++, normalize_map_pos(&i, &j)) {
      undo_add_tile(i, j);
      single_paint = 0; 
      paint_tile(i, j);
      single_paint = 1;
    }
  }

  /* we need the surrounding tiles as well */
  update_map_canvas(first_x - 1, first_y - 1, w + 3, h + 3, 1);

  refresh_overview_canvas();
  refresh_overview_viewrect();

  /* rectangle finished; reset values */
  first_x = -1; first_y = -1;
  last_x = -1; last_y = -1;
} 

/*****************************************************************************
 add a start position at (x,y)
 at this point, these will be raw starting positions. the server will fill
 them randomly with races when the game starts.
*****************************************************************************/
void put_start_position(gint x, gint y)
{
  /* all the logic is in startpos.c */

  if (add_start_position(x, y)) {
    paint_tile(x, y);
  }
}

/*****************************************************************************
 obvious
*****************************************************************************/
void put_city(gint x, gint y)
{
  if(game.player_idx == -1)
    append_output_window(_("You must make a nation active to place a city."));
  else{
    struct tile *ptile = map_get_tile(x,y);

    if(!city_can_be_built_here(x,y)){
      append_output_window(_("You can't build a city here."));
      return;
    }

    unit_list_iterate(ptile->units, punit){
      if(punit->owner != game.player_ptr->player_no){
        append_output_window(_("You can't place a city on "
                               "top of an enemy unit."));
        return;
      }
    } unit_list_iterate_end;

    if(default_pseudo_city_unfog) {
      map_city_radius_iterate(x, y, x_itr, y_itr) {
        map_set_known(x_itr, y_itr, game.player_ptr);
      } map_city_radius_iterate_end;
      map_unfog_pseudo_city_area(game.player_ptr, x, y);
    } else {
      /* settler would be there to found the city */
      unfog_area(game.player_ptr, x, y, 1);
    }
    create_city(game.player_ptr, x, y,
                city_name_suggestion(game.player_ptr, x, y));

    copy_city_defaults(map_get_city(x, y));

    update_nation_manager();
    update_map_canvas_visible();
    refresh_overview_canvas();
  }
}

/*****************************************************************************
 also obvious
*****************************************************************************/
void put_unit(gint x, gint y)
{
  struct player *pplayer = game.player_ptr;
  struct tile *ptile = map_get_tile(x, y);
  struct city *pcity = map_get_city(x, y);
  struct unit *punit;
  Unit_Type_id type = selected_unitpaint;

  if(game.player_idx == -1){
    append_output_window(_("You must make a nation active to place a unit."));
    return;
  }

  if(pcity && pplayer != city_owner(pcity)){
    append_output_window(_("You can't place a unit in an enemy city."));
    return;
  }

  if( (ptile->terrain == T_OCEAN && is_ground_unittype(type) && 
      !ground_unit_transporter_capacity(x, y, game.player_ptr)) 
      || (ptile->terrain != T_OCEAN && is_water_unit(type) 
      && !(pcity && is_at_coast(x, y)) ) ){
    append_output_window(_("You can't place the unit here."));
    return;
  }

  unit_list_iterate(ptile->units, punit){
    if(punit->owner != pplayer->player_no){
      append_output_window(_("You can't place a unit "
                             "on top of an enemy unit."));
      return; /* nope */
    }
  } unit_list_iterate_end;

  punit = create_unit_full(game.player_ptr, x, y, type, 0, 0, -1, -1);

  copy_unit_defaults(punit);

  update_attributes_dialog();
  update_unit_info_label(punit);
  update_nation_manager();
  update_map_canvas_visible();
  refresh_overview_canvas();
}

/*****************************************************************************
  delete a city or unit
*****************************************************************************/
void delete_unit(int x, int y)
{
  ed_delete_units(x, y);
  ed_delete_city(x, y);

  update_nation_manager();
  update_map_canvas_visible();
  refresh_overview_canvas();
}

/*****************************************************************************
copy: copy the rectangle of tiles into the clipboard
the parameters x,y are not currently used.
*****************************************************************************/
void end_copy(gint x, gint y)
{
  int i, j, u, v;
  int start_x, start_y, end_x, end_y;
  int catch;

  undraw_rect(FALSE);

  get_canvas_xy(first_x, first_y, &start_x, &start_y);
  catch = get_canvas_xy(last_x, last_y, &end_x, &end_y);

  if(!catch){  /* FIXME: should be able to... */
    append_output_window(_("You may not copy with an endpoint "
                           "outside the visible window"));
    first_x = -1; first_y = -1;
    last_x = -1; last_y = -1;
    return;
  }

  /* effect change in the square */
  if (start_x > end_x) {
    int tmp = first_x;
    first_x = last_x;
    last_x = tmp;
  }
  if (start_y > end_y) {
    int tmp = first_y;
    first_y = last_y;
    last_y = tmp;
  }

  init_clipboard(clip, last_x - first_x + 1, last_y - first_y + 1);

  normalize_map_pos(&first_x, &first_y);
  last_x++; last_y++;
  normalize_map_pos(&last_x, &last_y);
    
  for(u = 0, i = first_x; i != last_x; u++, i++, normalize_map_pos(&i, &j)) {
    for(v = 0, j = first_y; j != last_y; v++, j++, normalize_map_pos(&i, &j)) {
      clip->tiles[u][v] = *map_get_tile(i, j);
    }
  }

  /* rectangle finished; reset values */
  first_x = -1; first_y = -1;
  last_x = -1; last_y = -1;
}

/*****************************************************************************
Draw a rectangle the size of the tiles from the clipboard
*****************************************************************************/
void start_paste(gint x, gint y)
{
  undraw_rect(TRUE);
  first_x = x; first_y = y;
  last_x = x + clip->width - 1;
  last_y = y + clip->height - 1;
  normalize_map_pos(&last_x, &last_y);
  draw_rect();
}

/*****************************************************************************
paste: copy the rectangle of tiles from the clipboard;
*****************************************************************************/
void end_paste(gint x, gint y)
{
  int i, j, map_x, map_y;
  struct tile *ptile_map, *ptile_clip;
  undraw_rect(TRUE);

  create_new_undo_node();

  for (i = 0; i < clip->width; i++) {
    for (j = 0; j < clip->height; j++) {
      map_x = i + x; map_y = j + y;
      if (normalize_map_pos(&map_x, &map_y)) {
        undo_add_tile(map_x, map_y);
        ptile_map = map_get_tile(map_x, map_y);
        ptile_clip = &clip->tiles[i][j];
        ptile_map->terrain = ptile_clip->terrain;
        ptile_map->special = ptile_clip->special;
      }
    }
  }

  for (i = -1; i < clip->width + 1; i++) {
    for (j = -1; j < clip->height + 1; j++) {
      map_x = i + x; 
      map_y = j + y;
      if (normalize_map_pos(&map_x, &map_y)) {
        refresh_tile_mapcanvas(map_x, map_y, 1);
      }
    }
  }

  refresh_overview_viewrect();
}

[Prev in Thread] Current Thread [Next in Thread]