In the middle of updating pit-swing.

This commit is contained in:
Jonathan Bernard
2010-03-08 21:33:49 -06:00
parent a3f9f4b291
commit cd8cdf02a4
16 changed files with 660 additions and 427 deletions

View File

@ -0,0 +1,64 @@
package com.jdbernard.pit.swing
import com.jdbernard.pit.Category
import com.jdbernard.pit.Status
import java.awt.GridBagConstraints as GBC
import javax.swing.DefaultComboBoxModel
dialog = dialog(title: 'New Task...', modal: true, pack: true,
locationRelativeTo: null) {
gridBagLayout()
label('Title/Summary:',
constraints: gbc(gridx: 0, gridy: 0, gridwidth: 3,
insets: [5, 5, 0, 5], fill: GBC.HORIZONTAL))
titleTextField = textField(
constraints: gbc(gridx: 0, gridy: 1, gridwidth: 3,
insets: [0, 10, 0, 5], fill: GBC.HORIZONTAL),
keyTyped: { model.text = titleTextField.text })
label('Category:',
constraints: gbc(gridx: 0, gridy: 2, insets: [5, 5, 0, 0],
fill: GBC.HORIZONTAL))
categoryComboBox = comboBox(
constraints: gbc(gridx: 1, gridy: 2, insets: [5, 5, 0, 5],
fill: GBC.HORIZONTAL),
model: new DefaultComboBoxModel(Category.values()),
editable: false,
itemStateChanged: { model.category = categoryComboBox.selectedValue })
label('Status:',
constraints: gbc(gridx: 0, gridy: 3, insets: [5, 5, 0, 0],
fill: GBC.HORIZONTAL))
statusComboBox = comboBox(
constraints: gbc(gridx: 1, gridy: 3, insets: [5, 5, 0, 5],
fill: GBC.HORIZONTAL),
model: new DefaultComboBoxModel(Status.values()),
editable: false,
itemStateChanged: { model.status = statusComboBox.selectedValue })
label('Priority (0-9, 0 is highest priority):',
constraints: gbc(gridx: 0, gridy: 4, insets: [5, 5, 0, 0],
fill: GBC.HORIZONTAL))
prioritySpinner = spinner(
constraints: gbc( gridx: 1, gridy: 4, insets: [5, 5, 0, 5],
fill: GBC.HORIZONTAL),
model: spinnerNumberModel(maximum: 9, minimum: 0),
stateChanged: { model.priority = prioritySpinner.value })
button('Cancel',
actionPerformed: {
model.accept = false
dialog.visible = false
},
constraints: gbc(gridx: 0, gridy: 5, insets: [5, 5, 5, 5],
anchor: GBC.EAST))
button('Create Issue',
actionPerformed: {
model.accept = true
dialog.visible = false
},
constraints: gbc(gridx: 1, gridy: 5, insets: [5, 5, 5, 5],
anchor: GBC.WEST))
}

View File

@ -9,195 +9,45 @@ import com.jdbernard.pit.FileProject
import groovy.beans.Bindable
import java.awt.BorderLayout as BL
import java.awt.GridBagConstraints as GBC
import java.awt.Font
import java.awt.Point
import java.awt.event.MouseEvent
import javax.swing.DefaultComboBoxModel
import javax.swing.DefaultListModel
import javax.swing.JDialog
import javax.swing.JFileChooser
import javax.swing.JOptionPane
import javax.swing.JSplitPane
import javax.swing.JTextField
import javax.swing.ListSelectionModel
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.DefaultTreeCellRenderer
import javax.swing.tree.DefaultTreeModel
import javax.swing.tree.TreeSelectionModel
import net.miginfocom.swing.MigLayout
import java.awt.Color
actions {
action (
id: 'newIssue',
name: 'New Issue',
icon: imageIcon("/add.png"),
accelerator: shortcut('N'),
closure: controller.newIssue,
enabled: bind { model.selectedProject != null }
action(
id: 'openProject',
name: 'Open...',
icon: imageIcon('/folder.png'),
accelerator: shortcut('O'),
closure: controller.openProject
)
action (
id: 'createIssue',
name: 'Create Issue',
closure: controller.createIssue
)
action (
id: 'newProject',
name: 'New Project...',
icon: imageIcon("/add.png"),
closure: controller.newProject
)
action (
id: 'deleteProject',
name: 'Delete Project',
closure: controller.deleteProject,
enabled: bind {model.selectedProject != null }
)
action (
id: 'deleteProjectPop',
name: 'Delete Project',
icon: imageIcon("/delete.png"),
closure: controller.deleteProject,
enabled: bind { model.popupProject != null }
)
action (
id: 'deleteIssue',
name: 'Delete Issue',
icon: imageIcon("/delete.png"),
closure: controller.deleteIssue,
)
action (
id: 'deleteIssuePop',
name: 'Delete Issue',
icon: imageIcon("/delete.png"),
closure: controller.deleteIssue,
enabled: bind { model.popupIssue != null }
action(
id: 'shutdown',
name: 'Exit',
icon: imageIcon('/shutdown.png'),
accelerator: shortcut('x'),
closure: controller.shutdown
)
}
/* ****************
* GUI components
* ****************/
categoryIcons = [:]
statusIcons = [:]
// initialize category-related view data
Category.values().each {
categoryIcons[(it)] = imageIcon("/${it.name().toLowerCase()}.png")
model.categoryIcons[(it)] = imageIcon("/${it.name().toLowerCase()}.png")
model.filter.categories.add(it)
}
Status.values().each {
statusIcons[(it)] = imageIcon("/${it.name().toLowerCase()}.png")
model.statusIcons[(it)] = imageIcon("/${it.name().toLowerCase()}.png")
}
openDialog = fileChooser(fileSelectionMode: JFileChooser.DIRECTORIES_ONLY)
newIssueDialog = dialog(title: 'New Task...', modal: true, pack: true,
locationRelativeTo: null) {
gridBagLayout()
label('Title/Summary:',
constraints: gbc(gridx: 0, gridy: 0, gridwidth: 3,
insets: [5, 5, 0, 5], fill: GBC.HORIZONTAL))
titleTextField = textField(
constraints: gbc(gridx: 0, gridy: 1, gridwidth: 3,
insets: [0, 10, 0, 5], fill: GBC.HORIZONTAL))
label('Category:',
constraints: gbc(gridx: 0, gridy: 2, insets: [5, 5, 0, 0],
fill: GBC.HORIZONTAL))
categoryComboBox = comboBox(
constraints: gbc(gridx: 1, gridy: 2, insets: [5, 5, 0, 5],
fill: GBC.HORIZONTAL),
model: new DefaultComboBoxModel(Category.values()))
label('Status:',
constraints: gbc(gridx: 0, gridy: 3, insets: [5, 5, 0, 0],
fill: GBC.HORIZONTAL))
statusComboBox = comboBox(
constraints: gbc(gridx: 1, gridy: 3, insets: [5, 5, 0, 5],
fill: GBC.HORIZONTAL),
model: new DefaultComboBoxModel(Status.values()))
label('Priority (0-9, 0 is highest priority):',
constraints: gbc(gridx: 0, gridy: 4, insets: [5, 5, 0, 0],
fill: GBC.HORIZONTAL))
prioritySpinner = spinner(
constraints: gbc( gridx: 1, gridy: 4, insets: [5, 5, 0, 5],
fill: GBC.HORIZONTAL),
model: spinnerNumberModel(maximum: 9, minimum: 0))
button('Cancel', actionPerformed: { newIssueDialog.visible = false },
constraints: gbc(gridx: 0, gridy: 5, insets: [5, 5, 5, 5],
anchor: GBC.EAST))
button(createIssue,
constraints: gbc(gridx: 1, gridy: 5, insets: [5, 5, 5, 5],
anchor: GBC.WEST))
}
projectPopupMenu = popupMenu() {
menuItem(newProject)
menuItem(deleteProjectPop)
}
issuePopupMenu = popupMenu() {
menuItem(newIssue)
menuItem(deleteIssuePop)
separator()
menu('Change Category') {
Category.values().each { category ->
menuItem(category.toString(),
icon: categoryIcons[(category)],
enabled: bind { model.popupIssue != null },
actionPerformed: {
model.popupIssue.category = category
issueList.invalidate()
issueList.repaint()
})
}
}
menu('Change Status') {
Status.values().each { status ->
menuItem(status.toString(),
icon: statusIcons[(status)],
enabled: bind { model.popupIssue != null },
actionPerformed: {
model.popupIssue.status = status
issueList.invalidate()
issueList.repaint()
})
}
}
menuItem('Change Priority...',
enabled: bind { model.popupIssue != null },
actionPerformed: {
def newPriority = JOptionPane.showInputDialog(frame,
'New priority (0-9)', 'Change Priority...',
JOptionPane.QUESTION_MESSAGE)
try { model.popupIssue.priority = newPriority.toInteger() }
catch (exception) {
JOptionPane.showMessage(frame, 'The priority value must ' +
'be an integer in [0-9].', 'Change Priority...',
JOptionPane.ERROR_MESSAGE)
return
}
issueList.invalidate()
issueList.repaint()
})
}
frame = application(title:'Personal Issue Tracker',
minimumSize: [800, 500],
pack:true,
@ -211,16 +61,8 @@ frame = application(title:'Personal Issue Tracker',
// main menu
menuBar() {
menu("File") {
menuItem('Open...', actionPerformed: {
def projectDir
if (openDialog.showOpenDialog(frame) !=
JFileChooser.APPROVE_OPTION) return
projectDir = openDialog.selectedFile
model.rootProject = new FileProject(projectDir)
})
menuItem('Exit', actionPerformed: { app.shutdown() })
menuItem(openProject)
menuItem(shutdown)
}
menu('View') {
@ -302,127 +144,7 @@ frame = application(title:'Personal Issue Tracker',
}
}
gridBagLayout()
mainTabbedPane = tabbedPane() {
// main split view
splitPane(orientation: JSplitPane.HORIZONTAL_SPLIT,
dividerLocation: 280,
constraints: gbc(fill: GBC.BOTH, insets: [10,10,10,10],
weightx: 2, weighty: 2)) {
// left side (projects tree and buttons)
panel(constraints: "left") {
gridBagLayout()
// tree view of projects
scrollPane(constraints: gbc(fill: GBC.BOTH, gridx: 0, gridy:0,
gridwidth: 2, weightx: 2, weighty: 2)) {
treeCellRenderer = new DefaultTreeCellRenderer()
treeCellRenderer.leafIcon = treeCellRenderer.closedIcon
projectTree = tree(cellRenderer: treeCellRenderer,
model: bind(source: model, sourceProperty: 'rootProject',
sourceValue: {
if (model.rootProject) {
projectTree.rootVisible =
model.rootProject.issues.size()
new DefaultTreeModel(controller.makeNodes(
model.rootProject))
} else {
projectTree.rootVisible = false
new DefaultTreeModel(new DefaultMutableTreeNode())
}
}),
valueChanged: { evt ->
model.selectedProject = evt?.newLeadSelectionPath?.
lastPathComponent?.userObject ?: model.rootProject
controller.displayProject(model.selectedProject)
},
mouseClicked: { evt ->
if (evt.button == MouseEvent.BUTTON3) {
controller.showProjectPopup(
projectTree.getPathForLocation(evt.x, evt.y)
?.lastPathComponent?.userObject,
evt.x, evt.y)
}
})
projectTree.model = new DefaultTreeModel(
new DefaultMutableTreeNode())
projectTree.rootVisible = false
projectTree.selectionModel.selectionMode =
TreeSelectionModel.SINGLE_TREE_SELECTION
}
// project buttons
newProjectButton = button(newProject,
constraints: gbc(fill: GBC.NONE, gridx: 0, gridy: 1,
anchor: GBC.WEST))
deleteProjectButton = button(deleteProject,
constraints: gbc(fill: GBC.NONE, gridx: 1, gridy: 1,
anchor: GBC.WEST))
}
// split between issue list and issue details
splitPane(orientation: JSplitPane.VERTICAL_SPLIT,
dividerLocation: 200, constraints: "right") {
panel(constraints: "top") {
gridBagLayout()
scrollPane(constraints: gbc(fill: GBC.BOTH, weightx: 2,
weighty: 2, gridx: 0, gridy: 0, gridwidth: 3)) {
issueList = list(
cellRenderer: bind(source: model,
sourceProperty: 'issueListRenderer'),
selectionMode: ListSelectionModel.SINGLE_SELECTION,
valueChanged: { evt ->
controller.displayIssue(issueList.selectedValue)
},
mouseClicked: { evt ->
if (evt.button == MouseEvent.BUTTON3) {
issueList.selectedIndex = issueList.locationToIndex(
[evt.x, evt.y] as Point)
controller.showIssuePopup(
issueList.selectedValue, evt.x, evt.y)
}
})
}
wordWrapCheckBox = checkBox('Word wrap',
constraints: gbc(gridx: 0, gridy: 1, weightx: 2,
anchor: GBC.WEST), selected: true)
button(newIssue,
constraints: gbc(gridx: 1, gridy: 1, anchor: GBC.EAST))
deleteIssueButton = button(deleteIssue,
constraints: gbc(gridx: 2, gridy: 1, anchor: GBC.EAST),
enabled: bind(source: issueList, sourceEvent: 'valueChanged',
sourceValue: { issueList.selectedValue != null }))
}
scrollPane(constraints: "bottom") {
issueTextArea = textArea(
wrapStyleWord: true,
lineWrap: bind(source: wordWrapCheckBox,
sourceProperty: 'selected'),
editable: bind( source: issueList, sourceEvent: 'valueChanged',
sourceValue: { issueList.selectedValue != null }),
font: new Font(Font.MONOSPACED, Font.PLAIN, 10),
focusGained: {},
focusLost: {
if (!issueList?.selectedValue) return
if (issueTextArea.text != issueList.selectedValue.text)
issueList.selectedValue.text = issueTextArea.text
},
mouseExited: {
if (!issueList?.selectedValue) return
if (issueTextArea.text != issueList.selectedValue.text)
issueList.selectedValue.text = issueTextArea.text
})
}
}
}
}

View File

@ -0,0 +1,241 @@
package com.jdbernard.pit.swing
import com.jdbernard.pit.Category
import com.jdbernard.pit.Status
import com.jdbernard.pit.Project
import java.awt.Font
import java.awt.GridBagConstraints as GBC
import java.awt.Point
import java.awt.event.MouseEvent
import javax.swing.JOptionPane
import javax.swing.JSplitPane
import javax.swing.JTextField
import javax.swing.ListSelectionModel
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.DefaultTreeCellRenderer
import javax.swing.tree.DefaultTreeModel
import javax.swing.tree.TreeSelectionModel
actions {
action (
id: 'newIssue',
name: 'New Issue',
icon: imageIcon("/add.png"),
accelerator: shortcut('N'),
closure: controller.newIssue,
enabled: bind { model.selectedProject != null }
)
action (
id: 'newProject',
name: 'New Project...',
icon: imageIcon("/add.png"),
closure: controller.newProject
)
action (
id: 'deleteProject',
name: 'Delete Project',
closure: controller.deleteProject,
enabled: bind {model.selectedProject != null }
)
action (
id: 'deleteProjectPop',
name: 'Delete Project',
icon: imageIcon("/delete.png"),
closure: controller.deleteProject,
enabled: bind { model.popupProject != null }
)
action (
id: 'deleteIssue',
name: 'Delete Issue',
icon: imageIcon("/delete.png"),
closure: controller.deleteIssue,
)
action (
id: 'deleteIssuePop',
name: 'Delete Issue',
icon: imageIcon("/delete.png"),
closure: controller.deleteIssue,
enabled: bind { model.popupIssue != null }
)
}
// popup menu for projects
projectPopupMenu = popupMenu() {
menuItem(newProject)
menuItem(deleteProjectPop)
}
// popup menu for isses
issuePopupMenu = popupMenu() {
menuItem(newIssue)
menuItem(deleteIssuePop)
separator()
menu('Change Category') {
Category.values().each { category ->
menuItem(category.toString(),
icon: model.mainMVC.model.categoryIcons[(category)],
enabled: bind { model.popupIssue != null },
actionPerformed: {
model.popupIssue.category = category
issueList.invalidate()
issueList.repaint()
})
}
}
menu('Change Status') {
Status.values().each { status ->
menuItem(status.toString(),
icon: model.mainMVC.model.statusIcons[(status)],
enabled: bind { model.popupIssue != null },
actionPerformed: {
model.popupIssue.status = status
issueList.invalidate()
issueList.repaint()
})
}
}
menuItem('Change Priority...',
enabled: bind { model.popupIssue != null },
actionPerformed: {
def newPriority = JOptionPane.showInputDialog(mainMVC.view.frame,
'New priority (0-9)', 'Change Priority...',
JOptionPane.QUESTION_MESSAGE)
try { model.popupIssue.priority = newPriority.toInteger() }
catch (exception) {
JOptionPane.showMessageDialog(mainMVC.view.frame,
'The priority value must be an integer in [0-9].',
'Change Priority...', JOptionPane.ERROR_MESSAGE)
return
}
issueList.invalidate()
issueList.repaint()
})
}
// main split view
panel = splitPane(orientation: JSplitPane.HORIZONTAL_SPLIT,
// dividerLocation: bind(source: model.mainModel, property: dividerLocation),
constraints: gbc(fill: GBC.BOTH, insets: [10,10,10,10],
weightx: 2, weighty: 2)) {
// left side (projects tree and buttons)
panel(constraints: "left") {
gridBagLayout()
// tree view of projects
scrollPane(constraints: gbc(fill: GBC.BOTH, gridx: 0, gridy:0,
gridwidth: 2, weightx: 2, weighty: 2)) {
treeCellRenderer = new DefaultTreeCellRenderer()
treeCellRenderer.leafIcon = treeCellRenderer.closedIcon
projectTree = tree(cellRenderer: treeCellRenderer,
model: bind(source: model, sourceProperty: 'rootProject',
sourceValue: {
if (model.rootProject) {
projectTree.rootVisible =
model.rootProject.issues.size()
new DefaultTreeModel(controller.makeNodes(
model.rootProject))
} else {
projectTree.rootVisible = false
new DefaultTreeModel(new DefaultMutableTreeNode())
}
}),
valueChanged: { evt ->
model.selectedProject = evt?.newLeadSelectionPath?.
lastPathComponent?.userObject ?: model.rootProject
controller.displayProject(model.selectedProject)
},
mouseClicked: { evt ->
if (evt.button == MouseEvent.BUTTON3) {
controller.showProjectPopup(
projectTree.getPathForLocation(evt.x, evt.y)
?.lastPathComponent?.userObject,
evt.x, evt.y)
}
})
projectTree.model = new DefaultTreeModel(
new DefaultMutableTreeNode())
projectTree.rootVisible = false
projectTree.selectionModel.selectionMode =
TreeSelectionModel.SINGLE_TREE_SELECTION
}
// project buttons
newProjectButton = button(newProject,
constraints: gbc(fill: GBC.NONE, gridx: 0, gridy: 1,
anchor: GBC.WEST))
deleteProjectButton = button(deleteProject,
constraints: gbc(fill: GBC.NONE, gridx: 1, gridy: 1,
anchor: GBC.WEST))
}
// split between issue list and issue details
splitPane(orientation: JSplitPane.VERTICAL_SPLIT,
dividerLocation: 200, constraints: "right") {
panel(constraints: "top") {
gridBagLayout()
scrollPane(constraints: gbc(fill: GBC.BOTH, weightx: 2,
weighty: 2, gridx: 0, gridy: 0, gridwidth: 3)) {
issueList = list(
cellRenderer: model.issueCellRenderer,
selectionMode: ListSelectionModel.SINGLE_SELECTION,
valueChanged: { evt ->
controller.displayIssue(issueList.selectedValue)
},
mouseClicked: { evt ->
if (evt.button == MouseEvent.BUTTON3) {
issueList.selectedIndex = issueList.locationToIndex(
[evt.x, evt.y] as Point)
controller.showIssuePopup(
issueList.selectedValue, evt.x, evt.y)
}
})
}
wordWrapCheckBox = checkBox('Word wrap',
constraints: gbc(gridx: 0, gridy: 1, weightx: 2,
anchor: GBC.WEST), selected: true)
button(newIssue,
constraints: gbc(gridx: 1, gridy: 1, anchor: GBC.EAST))
deleteIssueButton = button(deleteIssue,
constraints: gbc(gridx: 2, gridy: 1, anchor: GBC.EAST),
enabled: bind(source: issueList, sourceEvent: 'valueChanged',
sourceValue: { issueList.selectedValue != null }))
}
scrollPane(constraints: "bottom") {
issueTextArea = textArea(
wrapStyleWord: true,
lineWrap: bind(source: wordWrapCheckBox,
sourceProperty: 'selected'),
editable: bind( source: issueList, sourceEvent: 'valueChanged',
sourceValue: { issueList.selectedValue != null }),
font: new Font(Font.MONOSPACED, Font.PLAIN, 10),
focusGained: {},
focusLost: {
if (!issueList?.selectedValue) return
if (issueTextArea.text != issueList.selectedValue.text)
issueList.selectedValue.text = issueTextArea.text
},
mouseExited: {
if (!issueList?.selectedValue) return
if (issueTextArea.text != issueList.selectedValue.text)
issueList.selectedValue.text = issueTextArea.text
})
}
}
}