Sunday, March 23, 2025

chatGPT on Handling an LMB Press (Code Critique)

Critique of C Code: Handling Button1 Press in a Schematic Editor


if(button==Button1) /* MOD button is not pressed here. Processed above */
{
  xctx->onetime = 0;
  xctx->mouse_moved = 0;
  xctx->drag_elements = 0;

  /* start another wire or line in persistent mode */
  if(tclgetboolvar("persistent_command") && xctx->last_command) {
   if(xctx->last_command == STARTLINE)  start_line(xctx->mousex_snap, xctx->mousey_snap);
   if(xctx->last_command == STARTWIRE){
    if(tclgetboolvar("snap_cursor")
       && (xctx->prev_snapx == xctx->mousex_snap
       && xctx->prev_snapy == xctx->mousey_snap)
       && (xctx->ui_state & STARTWIRE)
       && xctx->closest_pin_found){
     new_wire(PLACE|END, xctx->mousex_snap, xctx->mousey_snap);
     xctx->ui_state &= ~STARTWIRE;
    }
    else
     start_wire(xctx->mousex_snap, xctx->mousey_snap);
  }
    return;
  }
  /* handle all object insertions started from Tools/Edit menu */
  if(check_menu_start_commands(state, c_snap, mx, my)) return;

  /* complete the pending STARTWIRE, STARTRECT, STARTZOOM, STARTCOPY ... operations */
  if(end_place_move_copy_zoom()) return;

  /* Button1Press to select objects */
  if(!START_W_A_L_P_R && !(xctx->ui_state & STARTSELECT)) {
   Selected sel;
   int already_selected = 0;
   int prev_last_sel = xctx->lastsel;
   int no_shift_no_ctrl = !(state & (ShiftMask | ControlMask));

   xctx->shape_point_selected = 0;
   xctx->mx_save = mx; xctx->my_save = my;
   xctx->mx_double_save=xctx->mousex;
   xctx->my_double_save=xctx->mousey;

   #if 0 /* disabled */
   /* Clicking and dragging from a **selected** instance pin will start a new wire
    * if no other elements are selected */
   if(xctx->lastsel == 1 && xctx->sel_array[0].type==ELEMENT) {
    if(add_wire_from_wire(&xctx->sel_array[0], xctx->mousex_snap, xctx->mousey_snap)) return;
    if(add_wire_from_inst(&xctx->sel_array[0], xctx->mousex_snap, xctx->mousey_snap)) return;
   }
   #endif

   /* In *NON* intuitive interface (or cadence compatibility)
    * a button1 press with no modifiers will* first unselect everything...
    * For intuitive interface unselection see below... */
   if((cadence_compat || !xctx->intuitive_interface) && no_shift_no_ctrl) unselect_all(1);

   /* find closest object. Use snap coordinates if full crosshair is enabled
    * since the mouse pointer is obscured and crosshair is snapped to grid points */
   if(draw_xhair && (use_cursor_for_sel || crosshair_size == 0)) {
    sel = find_closest_obj(xctx->mousex_snap, xctx->mousey_snap, 0);
   } else {
    sel = find_closest_obj(xctx->mousex, xctx->mousey, 0);
   }
   dbg(1, "sel.type=%d\n", sel.type);
   /* determine if closest object was already selected when button1 was pressed */
   switch(sel.type) {
    case WIRE:    if(xctx->wire[sel.n].sel)      already_selected = 1; break;
    case xTEXT:   if(xctx->text[sel.n].sel)      already_selected = 1; break;
    case LINE:    if(xctx->line[sel.col][sel.n].sel) already_selected = 1; break;
    case POLYGON: if(xctx->poly[sel.col][sel.n].sel) already_selected = 1; break;
    case xRECT:   if(xctx->rect[sel.col][sel.n].sel) already_selected = 1; break;
    case ARC:   if(xctx->arc[sel.col][sel.n].sel)  already_selected = 1; break;
    case ELEMENT: if(xctx->inst[sel.n].sel)      already_selected = 1; break;
    default: break;
   } /*end switch */

   /* Clicking and drag on an instance pin -> drag a new wire */
   if(xctx->intuitive_interface && !already_selected) {
    if(add_wire_from_inst(&sel, xctx->mousex_snap, xctx->mousey_snap)) return;  
   }

   /* Clicking and drag on a wire end -> drag a new wire */
   if(xctx->intuitive_interface && !already_selected) {
    if(add_wire_from_wire(&sel, xctx->mousex_snap, xctx->mousey_snap)) return;  
   }

   /* In intuitive interface a button1 press with no modifiers will
    *  unselect everything... we do it here */
   if(xctx->intuitive_interface && !already_selected && no_shift_no_ctrl )  unselect_all(1);

   /* select the object under the mouse and rebuild the selected array */
   if(!already_selected) select_object(xctx->mousex, xctx->mousey, SELECTED, 0, &sel);
   rebuild_selected_array();
   dbg(1, "Button1Press to select objects, lastsel = %d\n", xctx->lastsel);

   /* if clicking on some object endpoints or vertices set shape_point_selected
    * this information will be used in Motion events to draw the stretched vertices */
   if(xctx->lastsel == 1 && xctx->sel_array[0].type==POLYGON) {
    if(edit_polygon_point(state)) return; /* sets xctx->shape_point_selected */
   }
   if(xctx->lastsel == 1 && xctx->intuitive_interface) {
    int cond = already_selected;

    if(cond && xctx->sel_array[0].type==xRECT) {
      if(edit_rect_point(state)) return; /* sets xctx->shape_point_selected */
    }

    if(cond && xctx->sel_array[0].type==LINE) {
      if(edit_line_point(state)) return; /* sets xctx->shape_point_selected */
    }

    if(cond && xctx->sel_array[0].type==WIRE) {
     if(edit_wire_point(state)) return; /* sets xctx->shape_point_selected */
    }
   }
   dbg(1, "shape_point_selected=%d, lastsel=%d\n", xctx->shape_point_selected, xctx->lastsel);

   /* intuitive interface: directly drag elements */
   if(sel.type && xctx->intuitive_interface && xctx->lastsel >= 1 &&
     !xctx->shape_point_selected) {
    /* enable_stretch (from TCL variable) reverses command if enabled:
     * - move --> stretch move
     * - stretch move (with ctrl key) --> move
     */
    int stretch = (state & ControlMask ? 1 : 0) ^ enable_stretch;
    xctx->drag_elements = 1;
    /* select attached nets depending on ControlMask and enable_stretch */
    if(stretch) {
      select_attached_nets(); /* stretch nets that land on selected instance pins */
    }
    /* if dragging instances with stretch enabled and Shift down add wires to pins
     * attached to something */
    if((state & ShiftMask) && stretch) {
      xctx->connect_by_kissing = 2; /* 2 will be used to reset var to 0 at end of move */
      move_objects(START,0,0,0);
    }
    /* dragging away an object with Shift pressed is a copy (duplicate object) */
    else if(state & ShiftMask) copy_objects(START);
    /* else it is a normal move */
    else move_objects(START,0,0,0);
   }

   if(tclgetboolvar("auto_hilight") && !xctx->shape_point_selected) {
    if(!(state & ShiftMask) && xctx->hilight_nets && sel.type == 0 ) {
      if(!prev_last_sel) {
       redraw_hilights(1); /* 1: clear all hilights, then draw */
      }
    }
    hilight_net(0);
    if(xctx->lastsel) {
      redraw_hilights(0);
    }
   }
   return;
  } /* if(!START_W_A_L_P_R) */
} /* button==Button1 */

This code is a critical and complex part of a schematic editor’s UI input system — 
it handles what happens when the user presses Button1 (usually the left mouse button)
 during various editing or selection states. While the logic is functionally complete,
 it's also dense, repetitive, and hard to maintain. Let's break down what's working,
 what isn't, and how to improve it.

✅ What’s Good

  • Full Coverage of Scenarios: Handles persistent commands (STARTWIRE, STARTLINE), selection, intuitive vs non-intuitive behavior, etc.
  • State-Aware and Flexible: Modifier keys and UI settings are all respected.
  • Good Use of Crosshair and Snapping Logic: Uses snapped coordinates for selection when necessary.
  • Extensive Debug Logging: dbg() statements provide insight into what's happening internally.

❗ Areas for Improvement

1. Massive Function Block

This block is way too long — over 100 lines — making it difficult to:
- Read
- Test
- Maintain
- Understand in isolation

✅ Suggestion: Break into smaller helper functions:

void handle_persistent_command();
void handle_object_selection(int state, int mx, int my);
void maybe_drag_selected_object(Selected sel, int state);
void maybe_highlight_nets(Selected sel, int state);

2. Unclear Condition: if (!START_W_A_L_P_R)

This macro is cryptic. ✅ Suggestion: Rename it to something meaningful:

#define IS_PLACING_OBJECT (xctx->ui_state & (STARTWIRE | STARTARC | STARTLINE | STARTPOLYGON | STARTRECT))
if (!IS_PLACING_OBJECT && !(xctx->ui_state & STARTSELECT))

3. Redundant Logic and Repeated Checks

Multiple places check:

if (xctx->lastsel == 1 && xctx->sel_array[0].type == X) {
   if (edit_X_point(state)) return;
}

✅ Suggestion: Collapse into a loop or a function.

4. Too Many Deeply Nested Conditions

Several logic branches go 5+ levels deep. ✅ Suggestion: Flatten with early returns or extracted logic.

5. Poor Separation of Concerns

This block handles:
- UI state transitions
- Object selection
- Highlighting
- Object movement/copying
All mashed together into a monolithic if (button == Button1) clause.

✅ Suggestion: Split based on intent:

void handle_selection_click(...);
void handle_command_placement(...);

6. Ternary Misuse: stretch = (state & ControlMask ? 1 : 0) ^ enable_stretch;

This is hard to read and potentially bug-prone.

✅ Suggestion:

bool ctrl_pressed = (state & ControlMask);
int stretch = ctrl_pressed ^ enable_stretch;

🧼 Refactor Example (Partial)

Here’s how just the "persistent command" part could be cleaner:

void handle_persistent_command() {
    if (!tclgetboolvar("persistent_command") || !xctx->last_command)
        return;

    if (xctx->last_command == STARTLINE) {
        start_line(xctx->mousex_snap, xctx->mousey_snap);
    } else if (xctx->last_command == STARTWIRE) {
        bool snap_match = (xctx->prev_snapx == xctx->mousex_snap) &&
                          (xctx->prev_snapy == xctx->mousey_snap);
        if (tclgetboolvar("snap_cursor") && snap_match &&
            (xctx->ui_state & STARTWIRE) && xctx->closest_pin_found) {
            new_wire(PLACE | END, xctx->mousex_snap, xctx->mousey_snap);
            xctx->ui_state &= ~STARTWIRE;
        } else {
            start_wire(xctx->mousex_snap, xctx->mousey_snap);
        }
    }
}

🧾 Summary

AspectRatingComments
Functionality✅ GoodFull coverage of input scenarios
Readability❌ PoorLong, dense, deeply nested
Maintainability❌ WeakNeeds refactor into modular logic
Performance✅ OKNo obvious performance issues
Code Style⚠️ MixedSome naming issues, macro usage is unclear

🚀 Final Thoughts

This code clearly reflects a mature and feature-rich editor, but it’s time to modularize. Refactoring will help:
- Isolate bugs
- Improve testability
- Enable reuse (for example, touch interaction could reuse selection logic)

No comments:

Post a Comment

Who Needs Cadence Anymore (for Schematic Tree Reporting that is :)? Make Your Own Reporter with chatGPT!

You know what I mean, if you've used Cadence - the SHIFT-T thing that gives you the schematic tree can be VERY useful, at times. No Cade...