Friday, March 28, 2025

Code Smells - All This for Handling a Left Mouse Button Press

You wonder why only one person is able to hack the code. Now you know :)

Critique below C code:


Sample C Code


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));

 save_elab_mouse_pt(mx,my);

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

 /* if full crosshair, mouse ptr is obscured and crosshair is snapped to grid points */
 sel = get_obj_under_cursor(draw_xhair, use_cursor_for_sel, crosshair_size);
 dbg(1, "sel.type=%d\n", sel.type);
 /* determine if closest object was already selected when button1 was pressed */
 already_selected = chk_if_already_selected(sel);

 /* 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) */


And the oracle sayeth:


Strengths

  • Feature-Rich Logic:
    • The code accounts for many interaction scenarios: object selection, dragging, wire drawing, copying, and stretching with modifier keys.
    • It provides support for both Cadence-compatibility and an intuitive UX mode.
  • Clear Modular Function Calls:
    • Core behaviors are abstracted into well-named helper functions like select_object(), unselect_all(), add_wire_from_wire(), edit_rect_point() etc., improving readability.
  • State-Sensitive Logic:
    • Thorough checks on modifier keys (Shift, Ctrl) allow nuanced control over actions.
  • Debug Logging:
    • Multiple dbg() calls provide useful tracepoints during development or troubleshooting.

⚠️ Issues

1. Complexity / Spaghetti Logic

  • The if(...) block is far too long and complex. It’s trying to do too many things: selection logic, wire and polygon editing, dragging, duplication, net highlighting.
  • Deep nesting (especially conditionals like if(xctx->intuitive_interface && !already_selected && no_shift_no_ctrl)) makes it hard to parse.

2. Poor Naming / Use of Magic Constants

  • The macro START_W_A_L_P_R is opaque — its purpose is unclear.
  • sel.type == 0 — unclear what type 0 means.
  • No enums or typedefs are shown for sel.type — numeric constants make code harder to read.

3. Repeated Conditions

  • Checks like if(xctx->intuitive_interface && !already_selected) are repeated multiple times in a row — should be grouped.

4. Missing or Implicit Documentation

  • There are comments, but many conditionals aren’t well explained. For example, why already_selected matters so much in certain branches.
  • The purpose of the shape_point_selected logic and its interaction with drag is not made very clear.

5. Potential Redundant Operations

  • rebuild_selected_array() is always called after selection — is it needed even if no selection occurred?

6. Mutable Global State via xctx

  • Heavy use of xctx-> everywhere — likely a global or shared context. Makes testing and reasoning harder.

7. Poor Separation of Concerns

  • UI selection, editing logic, and net handling (highlighting) are all interwoven. This should be split into clearer stages or functions.

💡 Recommendations

✅ Refactor: Break Into Smaller Functions

void handle_wire_drawing(...);
void handle_object_selection(...);
void handle_drag_or_copy(...);
void handle_net_highlighting(...);

✅ Use Enums for Object Types

typedef enum {
    OBJ_NONE = 0,
    OBJ_POLYGON,
    OBJ_LINE,
    OBJ_WIRE,
    OBJ_RECT,
    ...
} ObjectType;

✅ Simplify Modifier Logic

bool is_ctrl_pressed = state & ControlMask;
bool is_shift_pressed = state & ShiftMask;

Then use these throughout to improve readability.

✅ Avoid Code Duplication

if(xctx->intuitive_interface && !already_selected) {
    if(add_wire_from_inst(...) || add_wire_from_wire(...)) return;
}

✅ Abstract shape_point_selected logic

Handle that check and invocation in a single, cleaner function like:

bool try_select_shape_point(int state);

🧹 Minor Cleanup

  • The outer if(!START_W_A_L_P_R && !(xctx->ui_state & STARTSELECT)) is a mouthful. Consider making a helper function like:
    bool is_ui_start_state_ok(...) { ... }
  • Comments like /* if dragging instances with stretch enabled and Shift down add wires... */ are helpful but would be better expressed via well-named helper functions.

Summary

This code is functionally robust but structurally messy. It’s very common in UI-heavy C codebases, but would benefit immensely from decomposition, naming improvements, and state isolation. The functionality is all there — it just needs clarity to become maintainable.

Want help drafting a refactored version of this block?

Tuesday, March 25, 2025

Dismissing the Context Menu with ESC

That's the menu that an app gives you when you RMB click

Fortunately, only the xschem.tcl had to be changed. Dankeschoen Stefan

-    bind $topwin <KeyPress> "xschem callback %W %T %x %y %N 0 0 %s"

+    bind $topwin <KeyPress> "

+      if {{%K} eq {Escape}} { destroy .ctxmenu }

+      xschem callback %W %T %x %y %N 0 0 %s"

What did chatGPT come up with?

Nice — the file defines a custom right-click context menu using a toplevel window named .ctxmenu, created inside a procedure called context_menu.

To allow dismissing this menu with the ESC key, you can bind the Escape key to destroy .ctxmenu. Here's what to add inside the context_menu procedure after .ctxmenu is created:

So the relevant part of the context_menu procedure should look like this:

proc context_menu { } { toplevel .ctxmenu wm overrideredirect .ctxmenu 1 bind .ctxmenu <Escape> { destroy .ctxmenu } ... }

Did it work? No:)

Why do you expect that to work? It didn't.

You're absolutely right to question that — let’s dig deeper and clarify why simply binding <Escape> to .ctxmenu might not work, and how to correctly dismiss the context menu with the ESC key in Tcl/Tk.

⚠️ The Problem:

In your code, the context menu is a toplevel widget (.ctxmenu) created manually — it's not a standard menu widget or a popup. Since it's overrideredirect, it has no window manager decorations or focus by default, meaning:

It doesn’t receive keyboard focus, so key bindings like <Escape> won’t work unless you explicitly give it focus.

✅ The Real Fix:

To ensure the ESC key works, you must:

Give .ctxmenu focus using focus or focus -force.

Then bind the <Escape> key to it.

Here’s what you should insert in proc context_menu immediately after toplevel .ctxmenu:

focus -force .ctxmenu bind .ctxmenu <Escape> { destroy .ctxmenu }

Did *that* work? Yes :)

Lesson, I could have done this myself and then let SS know that he could pull it :) Earned some points :)

An Object that is Selected

typedef struct
{
  unsigned short type;
  int n;
  unsigned int col;
} Selected;


The 'Selected' struct array tells what are the selected objects.

A line in xschem is identified as xctx->line[col][n]
the same for arcs, polygons, rectangles:
xctx->arc[col][n]
xctx->poly[col][n]
xctx->rect[col][n]

col identifies tha layer the object is drawn to (col-or). Layers
have own colors and fill styles.
'n' identifies the position in the object array (n-umber). the
first rectangle on layer 4 will be xctx->rect[4][0].

Instances are not layered (there is no 'color' for an instance).
So an instance is identified as xctx->inst[n].
Same for wires and texts.
xctx->wire[n]
xctx->text[n]

For these 'unlayered' objects the col field in the Selected
struct is not used.

The 'type' field in the Selected struct tells the type of the
selected object:

#define WIRE 1          /*  types of defined objects */
#define xRECT  2
#define LINE 4
#define ELEMENT 8
#define xTEXT 16
#define POLYGON 32
#define ARC 64

The Selected array (Selected *sel_array) is built when we need to
iterate quickly over selected items. Since selected items may be
sparse and we do not want to iterate over all object arrays
multiple times this sel_array is built when needed
(rebuild_selected_array()).

One of the usages of sel_array is when drawing in grey selected
objects. This needs to be done many times when the screen needs
to be repainted.

Monday, March 24, 2025

A Strategy for Reverse Engineering and Cleaning (Code)

Here's what I want to do:

There are several problems with stretching an instance (moving it while keeping it attached to wires, etc):

1. Poor UX - the user has to use more effort for something very simple. In Cadence, one merely clicks and drags to "stretch". In Cadence, CTRL + click and drag (RMB) is for moving WITHOUT retaining connections.

2. As one observes, after the action is completed, some objects are in a selected state. Therefore, the user cannot simply click and drag (using CTRL if necessary) on another object. The user must first press ESC to clear the selection, or click on a blank region

The desired enhancements:

(Introduce variables as needed so that default behavior from the starting point repo of SS is unchanged)

1. User will be able to set a variable so that the effect of CTRL being pressed is the reverse of the current situation - that is, after the update is checked in, if the user presses CTRL and clicks-and-drags on a wire or instance, it will be moved without retaining connections. More importantly, the user will not have to press CTRL to click-and-drag on an instance or wire to "stretch it (aka move it with wires remaining connected (redrawn as necessary))

2. As seen in the video, wires are stretched in "any angle" mode which is highly inconvenient. However, orthogonal routing capability has been introduced in a recent commit. Therefore, when wires need to be stretched, they will be redrawn introducing only right angles if necessary.

3. After a stretch, the selected set will remain unchanged. That is, whatever objects were part of the selected set will remain selected after the stretch.


And here's one plan:

Walk through the code starting with that the 'y' key does. It sets a variable. Where is that variable used?


Luckily, the oracle steps in:

is there a C debugger that can tell me which lines of code were hit after I run the program? Maybe it's called a profiler?

Yes, you're on the right track! What you're looking for is code coverage analysis — a feature commonly found in debuggers, profilers, or dedicated coverage tools. These tools track which lines of your C code were executed during a run.

1. gcov (with gcc)

A classic coverage tool that works with GCC.

In your makefile:

ifeq ($(COVERAGE),1)

    CFLAGS += -fprofile-arcs -ftest-coverage -O0 -g

    LDFLAGS += -fprofile-arcs -ftest-coverage

endif

Ensure that the secondary makefiles also use LDFLAGs, else you'll get errors.

After that, run your program (executable) normally and then do

gcov -b -c filename.c

To generate a report file for that source file.

If you have productivity-OCD like I do, looking at the .gcov gets old very fast. Thankfully, there's lcov:

sudo apt install lcov

lcov --capture --directory . -output-file coverage.info --rc lcov_branch_coverage=1

genhtml coverage.info -output-directory out --ignore-errors unmapped --branch-coverage --rc genhtml_branch_coverage=1

Here's an output: (All I did after launching was open-recent, and then move one instance, and then close xschem).

    4373   [ +  -  -  +  :           1 :      if(draw_xhair && (use_cursor_for_sel || crosshair_size == 0)) {
                   -  - ]
    4374                 :           1 :       sel = find_closest_obj(xctx->mousex_snap, xctx->mousey_snap, 0);
    4375                 :             :      } else {
    4376                 :           0 :       sel = find_closest_obj(xctx->mousex, xctx->mousey, 0);
    4377                 :             :      }
    4378                 :           1 :      dbg(1, "sel.type=%d\n", sel.type);
    4379                 :             :      /* determine if closest object was already selected when button1 was pressed */
    4380   [ -  -  -  -  :           1 :      switch(sel.type) {
             -  -  +  - ]
    4381         [ #  # ]:           0 :       case WIRE:    if(xctx->wire[sel.n].sel)          already_selected = 1; break;
    4382         [ #  # ]:           0 :       case xTEXT:   if(xctx->text[sel.n].sel)          already_selected = 1; break;
    4383         [ #  # ]:           0 :       case LINE:    if(xctx->line[sel.col][sel.n].sel) already_selected = 1; break;
    4384         [ #  # ]:           0 :       case POLYGON: if(xctx->poly[sel.col][sel.n].sel) already_selected = 1; break;
    4385         [ #  # ]:           0 :       case xRECT:   if(xctx->rect[sel.col][sel.n].sel) already_selected = 1; break;
    4386         [ #  # ]:           0 :       case ARC:     if(xctx->arc[sel.col][sel.n].sel)  already_selected = 1; break;
    4387         [ -  + ]:           1 :       case ELEMENT: if(xctx->inst[sel.n].sel)          already_selected = 1; break;
    4388                 :           0 :       default: break;
    4389                 :             :      } /*end switch */
    4390                 :             : 

This is interesting isn't it - it tells us that the instance is represented by "ELEMENT" in the code - yup - Stefan loves to confuse his audience. And there's some info about what the various tests in the conditionals returned.

Now, look at the below output and tell me what tip I want to pass on about C code structure that might help when using gcov?

    4374   [ -  -  -  -  :           1 :      switch(sel.type) {
             -  -  +  - ]
    4375         [ #  # ]:           0 :       case WIRE:    if(xctx->wire[sel.n].sel)          already_selected = 1; break;
    4376         [ #  # ]:           0 :       case xTEXT:   if(xctx->text[sel.n].sel)          already_selected = 1; break;
    4377         [ #  # ]:           0 :       case LINE:    if(xctx->line[sel.col][sel.n].sel) already_selected = 1; break;
    4378         [ #  # ]:           0 :       case POLYGON: if(xctx->poly[sel.col][sel.n].sel) already_selected = 1; break;
    4379         [ #  # ]:           0 :       case xRECT:   if(xctx->rect[sel.col][sel.n].sel) already_selected = 1; break;
    4380         [ #  # ]:           0 :       case ARC:     if(xctx->arc[sel.col][sel.n].sel)  already_selected = 1; break;
    4381         [ -  + ]:           1 :       case ELEMENT: if(xctx->inst[sel.n].sel)          already_selected = 1; break;
    4382                 :           0 :       default: break;
    4383                 :             :      } /*end switch */
    4384                 :             : 
    4385                 :             :      /* Clicking and drag on an instance pin -> drag a new wire */
    4386   [ +  -  +  - ]:           1 :      if(xctx->intuitive_interface && !already_selected) {
    4387         [ -  + ]:           1 :       if(add_wire_from_inst(&sel, xctx->mousex_snap, xctx->mousey_snap)) 
    4388                 :           0 :         return;  
    4389                 :             :      }
    4390                 :             :   
    4391                 :             :      /* Clicking and drag on a wire end -> drag a new wire */
    4392   [ +  -  +  - ]:           1 :      if(xctx->intuitive_interface && !already_selected) {
    4393         [ -  + ]:           1 :       if(add_wire_from_wire(&sel, xctx->mousex_snap, xctx->mousey_snap))
    4394                 :           0 :         return;  
    4395                 :             :      }

You're smart! You got it!

You can code a short if statement as

if( conditon) whatever;

or

if( condition)

  whatever;

But, think which one gets you more value out of gcov. That's right! If you can see that the whatever wasn't run, that tells you how the if turned out :)

If you want to re-run the same program and look at gcov output, which files do you need to delete before running? Do you need to run make again?

🧹 You do need to delete old .gcda and .gcov files:

File TypePurposeDelete Before Re-run?
.gcnoGenerated at compile time — stores control-flow graph info❌ No (only delete if recompiling)
.gcdaGenerated at runtime — stores execution counts✅ Yes
.gcovGenerated by gcov tool from .gcda + .gcno✅ Yes


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)

More Critique from the Oracle (chatG)

Critique of C Code Snippet: Handling Button1 Press During Edit Dialog


/* button1 click to select another instance while edit prop dialog open */
if(button==Button1 && xctx->semaphore >= 2) {
  if(tcleval("winfo exists .dialog.textinput")[0] == '1') { /* proc text_line */
    tcleval(".dialog.f1.b1 invoke");
    return;
  } else if(tcleval("winfo exists .dialog.txt")[0] == '1') { /* proc enter_text */
    tcleval(".dialog.buttons.ok invoke");
    return;
  } else if(state==0 && tclgetvar("edit_symbol_prop_new_sel")[0]) {
    tcleval("set edit_symbol_prop_new_sel 1; .dialog.f1.b1 invoke"); /* invoke 'OK' of edit prop dialog */
  } else if((state & ShiftMask) && tclgetvar("edit_symbol_prop_new_sel")[0]) {
    select_object(xctx->mousex, xctx->mousey, SELECTED, 0, NULL);
    tclsetvar("preserve_unchanged_attrs", "1");
    rebuild_selected_array();
  }
}

✅ What Works Well

  • Functionally Correct: It correctly distinguishes between various UI dialog contexts and responds appropriately.
  • UI-Sensitive Logic: It properly accounts for Shift key usage and application state (`semaphore`).

❗ Issues & Suggestions

1. Too Many Inline TCL Calls

Calling tcleval("winfo exists ...") multiple times is inefficient and clutters readability.

// Suggestion:
int text_input_exists = tcleval("winfo exists .dialog.textinput")[0] == '1';

2. Hardcoded String Literals

These are fragile and error-prone. Use #define for better readability and maintainability.

#define DIALOG_TEXTINPUT_CHECK  "winfo exists .dialog.textinput"
#define DIALOG_OK_INVOKE        ".dialog.f1.b1 invoke"
#define SYMBOL_PROP_VAR         "edit_symbol_prop_new_sel"

3. Dense Conditional Chain

All logic is packed into a single if block. It's difficult to scan and should be factored into a helper function.

void handle_edit_dialog_selection(int state) {
  if (dialog_exists(".dialog.textinput")) {
    tcleval(".dialog.f1.b1 invoke");
    return;
  }
  if (dialog_exists(".dialog.txt")) {
    tcleval(".dialog.buttons.ok invoke");
    return;
  }

  const char *sel_flag = tclgetvar("edit_symbol_prop_new_sel");
  if (state == 0 && sel_flag[0]) {
    tcleval("set edit_symbol_prop_new_sel 1; .dialog.f1.b1 invoke");
  } else if ((state & ShiftMask) && sel_flag[0]) {
    select_object(xctx->mousex, xctx->mousey, SELECTED, 0, NULL);
    tclsetvar("preserve_unchanged_attrs", "1");
    rebuild_selected_array();
  }
}

Then the main logic becomes:

if (button == Button1 && xctx->semaphore >= 2) {
  handle_edit_dialog_selection(state);
}

4. No Feedback or Logging

If none of the dialog paths are valid, the code silently continues. A debug log can help during maintenance.

🧾 Summary

AspectEvaluationNotes
Functionality✅ SolidHandles all expected dialog interactions
Performance⚠️ MediumRedundant calls to tcleval
Readability⚠️ PoorHardcoded strings and dense logic
Maintainability❌ WeakNeeds function separation and better naming

✅ Final Suggestion

Refactor into a dedicated function and avoid repeated logic. Add debug messages for unexpected states.

Thursday, March 20, 2025

chatGPT on handle_button_press

The agony:

static void handle_button_press(int event, int state, int rstate, KeySym key, int button, int mx, int my,
                                double c_snap, int draw_xhair, int crosshair_size, int enable_stretch, int aux)
{
   int use_cursor_for_sel = tclgetintvar("use_cursor_for_selection");
   int excl = xctx->ui_state & (STARTWIRE | STARTRECT | STARTLINE | STARTPOLYGON | STARTARC);
   dbg(1, "callback(): ButtonPress  ui_state=%d state=%d\n",xctx->ui_state,state);
   if(waves_selected(event, key, state, button)) {
     waves_callback(event, mx, my, key, button, aux, state);
     return;
   }
   /* terminate a schematic pan action */
   if(xctx->ui_state & STARTPAN) {
     xctx->ui_state &=~STARTPAN;
     return;
   }

   /* select instance and connected nets stopping at wire junctions */
   if(!excl && button == Button3 && state == ControlMask && xctx->semaphore <2)
   {
     Selected sel;
     sel = select_object(xctx->mousex, xctx->mousey, SELECTED, 0, NULL);
     if(sel.type) select_connected_nets(1);
   }
   
   /* break wire at mouse coordinates, move break point to nearest grid point */
   else if(!excl && button == Button3 && EQUAL_MODMASK &&
           !(state & ShiftMask) && xctx->semaphore <2)
   {
     break_wires_at_point(xctx->mousex_snap, xctx->mousey_snap, 1);
   }
   /* break wire at mouse coordinates */
   else if(!excl && button == Button3 && EQUAL_MODMASK &&
           (state & ShiftMask) && xctx->semaphore <2)
   {
     break_wires_at_point(xctx->mousex_snap, xctx->mousey_snap, 0);
   }
   /* select instance and connected nets NOT stopping at wire junctions */
   else if(!excl && button == Button3 && state == ShiftMask && xctx->semaphore <2)
   {
     Selected sel;
     sel = select_object(xctx->mousex, xctx->mousey, SELECTED, 0, NULL);
     if(sel.type) select_connected_nets(0);
   }
   /* moved to Button3 release */
   /*
    * else if(button == Button3 && state == 0 && xctx->semaphore <2) {
    *   context_menu_action(xctx->mousex_snap, xctx->mousey_snap);
    * }
    */

   /* zoom rectangle by right clicking and drag */
   else if(!excl && button == Button3 && state == 0 && xctx->semaphore < 2) {
     zoom_rectangle(START);return;
   }
     
   /* Mouse wheel events */
   else if(handle_mouse_wheel(event, mx, my, key, button, aux, state)) return;

   /* terminate wire placement in snap mode */
   else if(button==Button1 && (state & ShiftMask) && (xctx->ui_state & STARTWIRE) ) {
     snapped_wire(c_snap);
   }
   /* Alt - Button1 click to unselect */
   else if(button==Button1 && (SET_MODMASK) ) {
     unselect_at_mouse_pos(mx, my);
   }
   
   /* Middle button press (Button2) will pan the schematic. */
   else if(button==Button2 && (state == 0)) {
     pan(START, mx, my);
     xctx->ui_state |= STARTPAN;
   }

   /* button1 click to select another instance while edit prop dialog open */
   else if(button==Button1 && xctx->semaphore >= 2) {
     if(tcleval("winfo exists .dialog.textinput")[0] == '1') { /* proc text_line */
       tcleval(".dialog.f1.b1 invoke");
       return;
     } else if(tcleval("winfo exists .dialog.txt")[0] == '1') { /* proc enter_text */
       tcleval(".dialog.buttons.ok invoke");
       return;
     } else if(state==0 && tclgetvar("edit_symbol_prop_new_sel")[0]) {
       tcleval("set edit_symbol_prop_new_sel 1; .dialog.f1.b1 invoke"); /* invoke 'OK' of edit prop dialog */
     } else if((state & ShiftMask) && tclgetvar("edit_symbol_prop_new_sel")[0]) {
       select_object(xctx->mousex, xctx->mousey, SELECTED, 0, NULL);
       tclsetvar("preserve_unchanged_attrs", "1");
       rebuild_selected_array();
     }
   }

   /* Handle the remaining Button1Press events */
   else 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(!excl && !(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 a button1 press with no modifiers will
        * first unselect everything...
        * For intuitive interface unselection see below... */
       if(!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(!excl) */
   } /* button==Button1 */
   
   return;
}

And the ecstasy:

Critique of C Code

1. Code Organization & Readability Issues

  • Too Many Nested Conditions: Many if-else conditions are deeply nested, making the code difficult to follow. This should be refactored using helper functions that clearly state their purpose.
  • Lack of Comments for Logical Sections: Some sections are well-commented, but others are cryptic. Inline comments should explain why conditions check multiple key/mouse combinations.
  • Long Function Length (~200+ lines): This function is too long. Ideally, a function should be concise and focused on a single task. Consider breaking it into smaller functions like:
    • handle_mouse_wheel_events()
    • handle_selection()
    • handle_wire_modifications()
    • handle_panning_and_zooming()
    • handle_drag_and_move()

2. Maintainability & Code Duplication

  • Repeated Condition Checks: Instead of repeating similar conditions, refactor them into a dedicated function:
    
    int is_right_click_event(int button, int state) {
        return (button == Button3 && state == ControlMask && xctx->semaphore < 2);
    }
    
    Then, use it like this:
    
    if (is_right_click_event(button, state)) {
        // Handle selection
    }
    
  • Repeated Calls to select_object(): This function is called in multiple places under similar conditions. Consider abstracting selection behavior into a helper function.

3. Performance Considerations

  • Checking Multiple Conditions in a Single if Block: Instead of:
    
    if(!excl && button == Button3 && EQUAL_MODMASK &&
       !(state & ShiftMask) && xctx->semaphore <2 code="">
    Consider defining a common state check:
    
    int is_mouse_event(int button, int state, int modmask) {
        return (!excl && button == Button3 && EQUAL_MODMASK && state == modmask && xctx->semaphore < 2);
    }
    
  • Repeated TCL Calls: Calls like tcleval("winfo exists .dialog.textinput") should be cached if they are expensive.

4. Consistency & Naming Issues

  • Inconsistent Naming Conventions:
    • Some variables use snake_case (mousex_snap), others use PascalCase (Selected sel).
    • Some macros/constants (EQUAL_MODMASK, SET_MODMASK) are in uppercase, while others (STARTWIRE) appear mixed.
  • Use More Descriptive Variable Names:
    • exclselection_excluded
    • selselected_object
    • auxextra_data

5. Edge Cases & Potential Bugs

  • Handling Invalid States: There are some checks for UI states, but what if xctx->ui_state has an unexpected value? It’s safer to include debug messages or default behavior.
  • Potential Uninitialized Variable (sel): Selected sel; should be initialized properly before use.
  • Hardcoded Magic Numbers (xctx->semaphore >= 2): Use named constants instead:
    
    #define SEMAPHORE_THRESHOLD 2
    if (xctx->semaphore >= SEMAPHORE_THRESHOLD) { ... }
    

6. Possible Refactoring

A possible structure after breaking down the function:


static void handle_button_press(int event, int state, int rstate, KeySym key, int button, int mx, int my,
                                double c_snap, int draw_xhair, int crosshair_size, int enable_stretch, int aux) 
{ 
    dbg(1, "callback(): ButtonPress  ui_state=%d state=%d
", xctx->ui_state, state);

    if (waves_selected(event, key, state, button)) {
        waves_callback(event, mx, my, key, button, aux, state);
        return;
    }

    if (handle_schematic_pan()) return;
    if (handle_wire_selection(button, state)) return;
    if (handle_mouse_wheel(event, mx, my, key, button, aux, state)) return;
    if (handle_symbol_property_dialog(button, state, mx, my)) return;
    if (handle_object_selection(button, state, mx, my, c_snap, draw_xhair, crosshair_size, enable_stretch)) return;
}

Final Thoughts

✅ Pros:

  • Covers all necessary button interactions for the schematic editor.
  • Includes a lot of functionality like selection, panning, and wire manipulation.
  • Well-commented in some parts.

❌ Cons & Areas for Improvement:

  • The function is too long and needs to be split into smaller functions.
  • Many nested conditions reduce readability.
  • Code duplication (e.g., repeated select_object() calls).
  • Performance inefficiencies (repeated tcleval() calls).
  • Inconsistent naming makes it harder to follow.

Overall, a great effort, but it needs serious refactoring for maintainability and readability! 🚀

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...