Easy drag and drop in contents as sibling or child

From SuperMemopedia
Jump to navigation Jump to search

Suggestion 1

Have you ever seen how Firefox handles bookmarks? It's easy to drag a bookmark (or a bunch of selected bookmarks) around and see where exactly they will be placed. If a bookmark will be placed into a folder (as a child), the folder becomes highlighted in blue. Conversely, a horizontal line appears if the bookmark will be inserted between two others (as a sibling). There are little arrows above or below this line when it is being placed at the "border" between bookmark X and the sibling of bookmark X's ancestor. (That probably made no sense whatsoever.) This would eliminate the need for the "Replace target" / "Add as last child" thing in the Contents window (which I tend to ignore, sometimes leading to unintended consequences when I've forgotten to change it). I notice that the visual features of this suggestion are already present when moving elements around in the Contents window, but they don't actually change the behavior.

Suggestion 2

Having to use "move target" and "replace target" in the Contents window is unintuitive, and these functions have unintuitive names. In most apps, to distinguish between moving something into an element and moving something between elements, the control simply uses the mouse's position: is it on top of an element or between them? Also, when trying to move these elements, the mouse cursor gets a little box with an arrow in it in the corner, indicating a behavior like creating a shortcut in Windows, but the actual behavior is nothing at all like creating a shortcut. This icon does nothing but confuse

Already implemented

Future SuperMemos will not include the mode box in contents (i.e. no Replace traget or Add as last child). The drag&drop behavior is similar to that of other Windows application. Dropping onto a node is equivalent to old Add as last child, while dropping in-between is equivalent to Replace target. Mode changes with Shift or Alt (known from older SuperMemos) will not be restored as redundant.

Comments

  • as always, some very useful functionality is missing from SuperMemo for technical reasons: the beauty of inserting element in-between branches is indisputable, but the component SuperMemo 2006 used to handle the contents tree did not have such a function (the component was chosen for its ability to handle Unicode). In the future, when Unicode is native to Delphi, different solutions will be chosen
  • cursor shapes displayed at drag&drop are in-built part of the component used by SuperMemo (not of SuperMemo itself)

User's response

Contents: It's not possible to modify Delphi's components? Can't inherit TreeView and write a custom OnStartDrag handler? After all, the functionality seems to be in place (with Replace target and Add as last child), just without a real-time switch while dragging. And the highlights and lines with arrows are already drawn (depending on whether the cursor is on top of a title or on whitespace). The only thing that should need to be added (as a quick solution) is a hook for whether the highlight or line is being displayed, and a change of functionality depending on which it is. Or, use pixel height: dragging an element to the top third of another one will add it before the other (as in Replace target), dragging to the middle third will add as child, and dragging to the bottom third will add after.

A solution adapted from here; maybe it will be useful:

procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer) ;
var
  ItemCursor, ItemAbove, ItemBelow, ItemSelected: TTreeNode;
  HTCursor: THitTests;
  AttachMode: TNodeAttachMode;
begin
  if TreeView1.Selected = nil then Exit;
  ItemSelected := TreeView1.Selected;

  { Let's hope that the coordinate axes start in the upper-left corner rather than the lower-left,
      otherwise we'll get faulty behavior. }
  { +/-6 comes from 18/3, where 18 is the height of an item in pixels. }
  HTCursor := TreeView1.GetHitTestInfoAt(X, Y);
  ItemCursor := TreeView1.GetNodeAt(X, Y);
  ItemAbove := TreeView1.GetNodeAt(X, Y-6);
  ItemBelow := TreeView1.GetNodeAt(X, Y+6);
  
  { I'm not so sure that htOnIcon is correct. Too bad I don't have a Delphi compiler.... }
  if (HTCursor - [htOnItem, htOnIcon, htOnStateIcon, htOnIndent, htOnRight] <> HTCursor) then
  begin
    if (ItemAbove = ItemCursor) and (ItemBelow = ItemCursor) then begin     {middle third of item}
      ItemSelected.MoveTo(ItemCursor, naAddChild);
      exit;
    end
    else if (ItemAbove <> ItemCursor) and (ItemBelow = ItemCursor) then begin   {top third of item}
      ItemSelected.MoveTo(ItemCursor, naInsert);
      exit;
    end
    else if (ItemAbove = ItemCursor) and (ItemBelow <> ItemCursor) then begin   {bottom third of item}
      ItemSelected.MoveTo(ItemCursor, naInsert);
      ItemCursor.MoveTo(ItemSelected, naInsert);
      exit;
    end;
  end;
end;