diff --git a/griffon-app/actions/com/jdbernard/timestamper/NotesDialogActions.groovy b/griffon-app/actions/com/jdbernard/timestamper/NotesDialogActions.groovy new file mode 100644 index 0000000..ea64952 --- /dev/null +++ b/griffon-app/actions/com/jdbernard/timestamper/NotesDialogActions.groovy @@ -0,0 +1,3 @@ +package com.jdbernard.timestamper + +def emptyAction = action(name: 'Empty Action') diff --git a/griffon-app/conf/Application.groovy b/griffon-app/conf/Application.groovy index b812f2e..dd667ae 100755 --- a/griffon-app/conf/Application.groovy +++ b/griffon-app/conf/Application.groovy @@ -9,6 +9,14 @@ application { //frameClass = 'javax.swing.JFrame' } mvcGroups { + // MVC Group for "com.jdbernard.timestamper.NotesDialog" + 'NotesDialog' { + actions = 'com.jdbernard.timestamper.NotesDialogActions' + model = 'com.jdbernard.timestamper.NotesDialogModel' + controller = 'com.jdbernard.timestamper.NotesDialogController' + view = 'com.jdbernard.timestamper.NotesDialogView' + } + // MVC Group for "com.jdbernard.timestamper.TimeStamperMain" 'TimeStamperMain' { actions = 'com.jdbernard.timestamper.TimeStamperMainActions' diff --git a/griffon-app/controllers/com/jdbernard/timestamper/NotesDialogController.groovy b/griffon-app/controllers/com/jdbernard/timestamper/NotesDialogController.groovy new file mode 100644 index 0000000..08498c4 --- /dev/null +++ b/griffon-app/controllers/com/jdbernard/timestamper/NotesDialogController.groovy @@ -0,0 +1,26 @@ +package com.jdbernard.timestamper + +import java.awt.Point +import java.awt.Rectangle +import java.awt.Toolkit + +class NotesDialogController { + // these will be injected by Griffon + def model + def view + + + void mvcGroupInit(Map args) { + } + + Point mousePressRelativeToDialog + + def mousePressed = { evt -> mousePressRelativeToDialog = evt?.point } + + def mouseDragged = { evt -> + GUIUtil.componentDragged(view.notesDialog, evt, + mousePressRelativeToDialog, + new Rectangle(Toolkit.defaultToolkit.screenSize), + app.views.TimeStamperMain.frame.bounds) + } +} diff --git a/griffon-app/controllers/com/jdbernard/timestamper/TimeStamperMainController.groovy b/griffon-app/controllers/com/jdbernard/timestamper/TimeStamperMainController.groovy index 190cdf0..0d18a19 100644 --- a/griffon-app/controllers/com/jdbernard/timestamper/TimeStamperMainController.groovy +++ b/griffon-app/controllers/com/jdbernard/timestamper/TimeStamperMainController.groovy @@ -1,12 +1,24 @@ package com.jdbernard.timestamper +import java.awt.Point +import java.awt.Rectangle +import java.awt.Toolkit +import java.util.Timer + class TimeStamperMainController { // these will be injected by Griffon def model def view + Timer updateTimer + + Point mousePressRelativeToFrame + void mvcGroupInit(Map args) { - // this method is called after model and view are injected + def notes = buildMVCGroup('NotesDialog') + view.notesDialog = notes.view.notesDialog + + updateTimer } def exitGracefully = { evt = null -> @@ -16,4 +28,22 @@ class TimeStamperMainController { def showToolsMenu = { evt = null -> } + + def showNotes = { evt = null -> + view.notesDialog.visible = view.notesVisibleButton.selected + } + + def showPunchcard = { evt = null -> + + } + + def mousePressed = { evt = null -> + mousePressRelativeToFrame = evt?.point + } + + def mouseDragged = { evt = null -> + GUIUtil.componentDragged(view.frame, evt, mousePressRelativeToFrame, + new Rectangle(Toolkit.defaultToolkit.screenSize)) + } + } diff --git a/griffon-app/models/com/jdbernard/timestamper/NotesDialogModel.groovy b/griffon-app/models/com/jdbernard/timestamper/NotesDialogModel.groovy new file mode 100644 index 0000000..0de4ba2 --- /dev/null +++ b/griffon-app/models/com/jdbernard/timestamper/NotesDialogModel.groovy @@ -0,0 +1,7 @@ +package com.jdbernard.timestamper + +import groovy.beans.Bindable + +class NotesDialogModel { + +} diff --git a/griffon-app/views/com/jdbernard/timestamper/NotesDialogView.groovy b/griffon-app/views/com/jdbernard/timestamper/NotesDialogView.groovy new file mode 100644 index 0000000..b302f60 --- /dev/null +++ b/griffon-app/views/com/jdbernard/timestamper/NotesDialogView.groovy @@ -0,0 +1,28 @@ +package com.jdbernard.timestamper + +import java.awt.Color +import javax.swing.BoxLayout +import net.miginfocom.swing.MigLayout + +notesDialog = dialog( + title: 'Notes', + modal: false, + undecorated: true, + minimumSize: [325, 200], + iconImage: imageIcon('/16-em-pencil.png').image, + iconImages: [imageIcon('/16-em-pencil.png').image] +) { + + panel( + border:lineBorder(color: Color.BLACK, thickness:1, parent:true), + mousePressed: controller.&mousePressed, + mouseDragged: controller.&mouseDragged, + layout: new MigLayout('insets 5 5 5 5, fill') + ) { + scrollPane(constraints: 'growx, growy') { + notesTextArea = textArea(lineWrap: true, columns: 20, rows: 5, + wrapStyleWord: true) + } + } + +} diff --git a/griffon-app/views/com/jdbernard/timestamper/TimeStamperMainView.groovy b/griffon-app/views/com/jdbernard/timestamper/TimeStamperMainView.groovy index 3703b4b..9f869ad 100644 --- a/griffon-app/views/com/jdbernard/timestamper/TimeStamperMainView.groovy +++ b/griffon-app/views/com/jdbernard/timestamper/TimeStamperMainView.groovy @@ -5,7 +5,7 @@ import java.awt.Font import javax.swing.BoxLayout import net.miginfocom.swing.MigLayout -application(title:'TimeStamper', +frame = application(title:'TimeStamper', //size:[320,480], pack:true, //location:[50,50], @@ -17,7 +17,9 @@ application(title:'TimeStamper', ) { panel( border:lineBorder(color:Color.BLACK, thickness:1, parent:true), - layout: new MigLayout('insets 0 5 0 0, fill','', '[]0[]0[]') + layout: new MigLayout('insets 0 5 0 0, fill','', '[]0[]0[]'), + mousePressed: controller.&mousePressed, + mouseDragged: controller.&mouseDragged ) { def mainFont = new Font(Font.SANS_SERIF, Font.BOLD, 12) def timeFont = new Font(Font.SANS_SERIF, Font.BOLD, 14) @@ -45,10 +47,12 @@ application(title:'TimeStamper', panel(constraints: 'alignx leading, aligny top, gapright 5px, wrap') { boxLayout(axis: BoxLayout.X_AXIS) - toggleButton(showNotesAction, icon: imageIcon('/16-em-pencil.png'), + notesVisibleButton = toggleButton(showNotesAction, icon: imageIcon('/16-em-pencil.png'), + hideActionText: true, border: emptyBorder(4)) - toggleButton(showPunchcardAction, + punchcardVisibleButton = toggleButton(showPunchcardAction, icon: imageIcon('/16-file-archive.png'), + hideActionText: true, border: emptyBorder(4)) } diff --git a/src/main/com/jdbernard/timestamper/GUIUtil.groovy b/src/main/com/jdbernard/timestamper/GUIUtil.groovy new file mode 100644 index 0000000..fbd01c5 --- /dev/null +++ b/src/main/com/jdbernard/timestamper/GUIUtil.groovy @@ -0,0 +1,91 @@ +package com.jdbernard.timestamper + +import java.awt.Point +import java.awt.Rectangle +import java.awt.Toolkit +import java.awt.event.MouseEvent + +public class GUIUtil { + + static Point calculateWindowMovement(Point currentMousePoint, + Point mousePressRelativeToWindow, Rectangle windowBounds, + Rectangle... snapBoxes) { + + // this is the point we will compute as the new upper left + // coordinate of the frame. It needs to be translated some to account + // for the fact that the user can press the mouse anywhere on the + // main panel. + Point currentWindowPoint = (Point) currentMousePoint.clone(); + // find out where the new point should be, ignoreing screen bounds + currentWindowPoint.translate((int) -mousePressRelativeToWindow.x, + (int) -mousePressRelativeToWindow.y); + + Point finalWindowPoint = (Point) currentWindowPoint.clone(); + + // snap tests. In the event of a conflict in snaps positions, the + // last snap wins + int wUp = windowBounds.y; + int wDown = windowBounds.y + windowBounds.height; + int wLeft = windowBounds.x; + int wRight = windowBounds.x + windowBounds.width; + +// currentTaskLabel.setText("U:" + wUp + " D:" + wDown + " L:" + wLeft +// + " R:" + wRight); + + for (Rectangle r : snapBoxes) { + int rUp = r.y; + int rDown = r.y + r.height; + int rLeft = r.x; + int rRight = r.x + r.width; + + // check to see if the window is near any of the snap boundaries + // if it is, 'snap' the final point to that boundary. + if (Math.abs(rUp - wUp) < 20) // top of window to top of rect + finalWindowPoint.y = rUp; + else if (Math.abs(rUp - wDown) < 20) // bot of w to top of r + finalWindowPoint.y = rUp - windowBounds.height; + else if (Math.abs(rDown - wUp) < 20) // top of w to bot of r + finalWindowPoint.y = rDown; + else if (Math.abs(rDown - wDown) < 20) // bot of w to bot of r + finalWindowPoint.y = rDown - windowBounds.height; + + if (Math.abs(rLeft - wLeft) < 20) // left of w to left of r + finalWindowPoint.x = rLeft; + else if (Math.abs(rLeft - wRight) < 20) // right of w to left of r + finalWindowPoint.x = rLeft - windowBounds.width; + else if (Math.abs(rRight - wLeft) < 20) // left of w to right of r + finalWindowPoint.x = rRight; + else if (Math.abs(rRight - wRight) < 20) // right of w to right of r + finalWindowPoint.x = rRight - windowBounds.width; + } + + // this point represents a backwards version of the initial calculation + // to find the finalPoint. It says, based on the current final point, + // assume I moved the frame to that point. Where, relative to that point + // should the mouse be, based on how far it was relative to the point + // when the user pressed it. + Point whereMouseShouldBe = (Point) finalWindowPoint.clone(); + whereMouseShouldBe.translate((int) mousePressRelativeToWindow.x, + (int) mousePressRelativeToWindow.y); + + // if the actual mouse location is different from the expected location, + // then we know that the snapping behaviour has altered the frames new + // placement. If this alteration is too large (30 px apart in this case) + // then we know the user is trying to pull the frame out of its snapped + // position. In this case, we want to ignore the snap calculations and + // base the new frame location entirely on the current mouse location + if (whereMouseShouldBe.distance(currentMousePoint) > 30) { + finalWindowPoint = (Point) currentMousePoint.clone(); + finalWindowPoint.translate((int) -mousePressRelativeToWindow.x, + (int) -mousePressRelativeToWindow.y); + } + + return finalWindowPoint; + } + + def static componentDragged(frame, MouseEvent evt, + Point mousePressRelativeToFrame, Rectangle... snapBoxes) { + frame.location = calculateWindowMovement(evt.getLocationOnScreen(), + mousePressRelativeToFrame, frame.bounds, snapBoxes) + } +} diff --git a/test/integration/NotesDialogTests.groovy b/test/integration/NotesDialogTests.groovy new file mode 100644 index 0000000..8eb01cc --- /dev/null +++ b/test/integration/NotesDialogTests.groovy @@ -0,0 +1,10 @@ +import griffon.util.IGriffonApplication + +class NotesDialogTests extends GroovyTestCase { + + IGriffonApplication app + + void testSomething() { + + } +}