Patch for nudge keys

I wanted nudge keys in the subcomponent layout editor to prevent wrist cramping, so I hacked Jucer to add them. A patch, made w/ diff -Naur, is at the end of this post.

I have tried to keep to Mr Storer’s coding conventions as I understand them, but only he knows whether the hack was done well or not. (Anyway I think I learned a good deal about JUCE so it wasn’t a waste for me.)

Usage when only one element is selected:

  • Arrow keys move selected element by one pixel
  • Arrow keys + alt move selected element according to the snap grid
  • Up / down + shift change the height by moving the bottom edge by one pixel
  • Left / right + shift change the width by moving the right edge by one pixel
  • Up / down + shift + alt change the height by moving the bottom edge to the snap grid
  • Left / right + shift + alt change the width by moving the right edge to the snap grid

Usage when many elements are selected:

  • Arrow keys move selected element by one pixel
  • Arrow keys + alt move selected element by n pixels where n is the snap resolution
  • Up / down + shift change the height by moving the bottom edge by one pixel
  • Left / right + shift change the width by moving the right edge by one pixel
  • Up / down + shift + alt change the height by n pixels where n is the snap resolution
  • Left / right + shift + alt change the width by n pixels where n is the snap resolution

The difference is only in snapping. When many elements are selected no attempt is made to snap one of them to the grid; instead the snap size is used as an increment only. This is similar to how dragging works.

Snapping is a bit inelegant when the component bounds are not on the grid – the dimension may jump over a snap line. I don’t care about this enough to fix it, but I did ensure that the snap won’t go backwards. (Of course this only applies to single selections)

Here is the patch:

diff -Naur -x '*~' c:/juce/jucer/src/model/jucer_ComponentLayout.cpp c:/juce/jucer-mpa/src/model/jucer_ComponentLayout.cpp
--- c:/juce/jucer/src/model/jucer_ComponentLayout.cpp	2006-06-07 11:57:00.000000000 -0700
+++ c:/juce/jucer-mpa/src/model/jucer_ComponentLayout.cpp	2006-07-06 20:42:12.000000000 -0700
@@ -504,9 +504,9 @@
     document->getUndoManager().beginNewTransaction();
 }
 
-void ComponentLayout::dragSelectedComps (int dx, int dy)
+void ComponentLayout::dragSelectedComps (int dx, int dy, bool allowSnap)
 {
-    if (document != 0 && selected.getNumSelected() > 1)
+    if (allowSnap && (document != 0 && selected.getNumSelected() > 1))
     {
         dx = document->snapPosition (dx);
         dy = document->snapPosition (dy);
@@ -521,8 +521,8 @@
 
         if (document != 0 && selected.getNumSelected() == 1)
         {
-            c->setTopLeftPosition (document->snapPosition (startX + dx),
-                                   document->snapPosition (startY + dy));
+            c->setTopLeftPosition (allowSnap ? document->snapPosition (startX + dx) : startX + dx,
+                                   allowSnap ? document->snapPosition (startY + dy) : startY + dy);
         }
         else
         {
@@ -560,6 +560,48 @@
     document->getUndoManager().beginNewTransaction();
 }
 
+void ComponentLayout::moveSelectedComps (int dx, int dy, bool snap)
+{
+    startDragging();
+    dragSelectedComps (dx, dy, snap);
+    endDragging();
+}
+
+void ComponentLayout::stretchSelectedComps (int dw, int dh, bool allowSnap)
+{
+    int neww,newh;
+    if (document != 0 && selected.getNumSelected() == 1)
+    {
+        Component *const c = selected.getSelectedItem(0);
+        int oldw = c->getWidth();
+        int oldh = c->getHeight();
+        if (allowSnap) {
+            int bot = c->getY() + oldh + dh;
+            int right = c->getX() + oldw + dw;
+            bot = dh ? document->snapPosition (bot) : bot;
+            right = dw ? document->snapPosition (right) : right;
+            newh = bot - c->getY();
+            neww = right - c->getX();
+        } else {
+            newh = oldh + dh;
+            neww = oldw + dw;
+        }
+        c->setSize (neww, newh);
+
+        updateStoredComponentPosition (c, true);
+    } 
+    else for (int i = 0; i < selected.getNumSelected(); ++i)
+    {
+        Component* const c = selected.getSelectedItem (i);
+
+        neww = c->getWidth() + dw;
+        newh = c->getHeight() + dh;
+        c->setSize (neww,newh);
+
+        updateStoredComponentPosition (c, true);
+    }
+}
+
 //==============================================================================
 void ComponentLayout::fillInGeneratedCode (GeneratedCode& code) const
 {
diff -Naur -x '*~' c:/juce/jucer/src/model/jucer_ComponentLayout.h c:/juce/jucer-mpa/src/model/jucer_ComponentLayout.h
--- c:/juce/jucer/src/model/jucer_ComponentLayout.h	2006-06-07 11:57:00.000000000 -0700
+++ c:/juce/jucer-mpa/src/model/jucer_ComponentLayout.h	2006-07-06 19:36:40.000000000 -0700
@@ -86,9 +86,12 @@
     void selectedToBack();
 
     void startDragging();
-    void dragSelectedComps (int dxFromDragStart, int dyFromDragStart);
+    void dragSelectedComps (int dxFromDragStart, int dyFromDragStart, bool allowSnap = true);
     void endDragging();
 
+    void moveSelectedComps (int dx, int dy, bool snap);
+    void stretchSelectedComps (int dw, int dh, bool allowSnap);
+
     void bringLostItemsBackOnScreen (int width, int height);
 
     //==============================================================================
diff -Naur -x '*~' c:/juce/jucer/src/ui/jucer_ComponentLayoutEditor.cpp c:/juce/jucer-mpa/src/ui/jucer_ComponentLayoutEditor.cpp
--- c:/juce/jucer/src/ui/jucer_ComponentLayoutEditor.cpp	2006-06-07 11:57:00.000000000 -0700
+++ c:/juce/jucer-mpa/src/ui/jucer_ComponentLayoutEditor.cpp	2006-07-06 20:31:00.000000000 -0700
@@ -282,6 +282,50 @@
         layout.getSelectedSet().deselectAll();
 }
 
+static void moveOrStretch (ComponentLayout& layout, int x, int y, bool snap, bool stretch)
+{
+    if (stretch)
+        layout.stretchSelectedComps (x, y, snap);
+    else
+        layout.moveSelectedComps (x, y, snap);
+}
+
+void ComponentLayoutEditor::keyPressed (const KeyPress& key)
+{
+    int kc = key.getKeyCode();
+    int amt;
+    bool snap,stretch;
+
+    snap = key.getModifiers().isAltDown();
+    stretch = key.getModifiers().isShiftDown();
+
+    if (snap)
+        amt = document.getSnappingGridSize()+1;
+    else
+        amt = 1;
+
+    if (kc == KeyPress::rightKey)
+    {
+        moveOrStretch (layout, amt, 0, snap, stretch);
+    }
+    else if (kc == KeyPress::downKey)
+    {
+        moveOrStretch (layout, 0,amt, snap, stretch);
+    }
+    else if (kc == KeyPress::leftKey)
+    {
+        moveOrStretch (layout, -amt, 0, snap, stretch);
+    }
+    else if (kc == KeyPress::upKey)
+    {
+        moveOrStretch (layout, 0, -amt, snap, stretch);
+    }
+    else
+    {
+        Component::keyPressed (key);
+    }
+}
+
 bool ComponentLayoutEditor::filesDropped (const StringArray& filenames, int x, int y)
 {
     File f (filenames [0]);
diff -Naur -x '*~' c:/juce/jucer/src/ui/jucer_ComponentLayoutEditor.h c:/juce/jucer-mpa/src/ui/jucer_ComponentLayoutEditor.h
--- c:/juce/jucer/src/ui/jucer_ComponentLayoutEditor.h	2006-06-07 11:57:00.000000000 -0700
+++ c:/juce/jucer-mpa/src/ui/jucer_ComponentLayoutEditor.h	2006-07-06 20:30:54.000000000 -0700
@@ -73,6 +73,8 @@
 
     const Rectangle getComponentArea() const;
 
+    void keyPressed (const KeyPress& key);
+
     //==============================================================================
     juce_UseDebuggingNewOperator
 

[/code][/list]

Cool - I can add that to the build if you like? (it’ll be just in time for the next release, which I’m going to try to do today).

Absolutely you can include it – it’s all yours if you like it! best --mpa

Already did!
(TBH it should have been done using application command shortcuts rather than intercepting keyPressed() directly, but I couldn’t be bothered doing all the extra typing…)

Ah, you’re right … well, umm … I like your excuse :slight_smile:

Personally I would never invoke these commands from menus or buttons etc. but certainly app commands are the right way to do things here. Next time then …