Restructered pit-swing to better follow MVC paradigm.
Adding extensibility features for pit-swing.
This commit is contained in:
parent
d77f04f12e
commit
a3f9f4b291
@ -1 +1,2 @@
|
||||
Load only N project deep at a time, lazy load any more.
|
||||
Load only N project deep at a time, lazy load any more.
|
||||
=======================================================
|
2
issues/pit-swing/0015fs3.rst
Normal file
2
issues/pit-swing/0015fs3.rst
Normal file
@ -0,0 +1,2 @@
|
||||
Add a runtime configuration file.
|
||||
=================================
|
2
issues/pit-swing/0016fs3.rst
Normal file
2
issues/pit-swing/0016fs3.rst
Normal file
@ -0,0 +1,2 @@
|
||||
Add templates for new issues.
|
||||
=============================
|
@ -9,6 +9,8 @@ public abstract class Issue {
|
||||
protected Status status
|
||||
protected int priority
|
||||
protected String text
|
||||
protected Date deliveryDate
|
||||
protected Date creationDate
|
||||
|
||||
Issue(String id, Category c = Category.TASK, Status s = Status.NEW,
|
||||
int p = 9) {
|
||||
@ -16,6 +18,8 @@ public abstract class Issue {
|
||||
this.category = c
|
||||
this.status = s
|
||||
this.priority = p
|
||||
this.creationDate = new Date()
|
||||
this.deliveryDate = null
|
||||
}
|
||||
|
||||
public String getId() { return id; }
|
||||
@ -48,6 +52,14 @@ public abstract class Issue {
|
||||
|
||||
public void setText(String t) { text = t }
|
||||
|
||||
public boolean hasDelivery() { return deliveryDate == null }
|
||||
|
||||
public Date getCreationDate() { return creationDate }
|
||||
|
||||
public Date getDeliveryDate() { return deliveryDate }
|
||||
|
||||
public void setDeliveryDate(Date dd) { deliveryDate = dd }
|
||||
|
||||
@Override
|
||||
public String toString() { return "${id}(${priority}-${status}): ${category} ${title}" }
|
||||
|
||||
|
@ -1,17 +1,166 @@
|
||||
package com.jdbernard.pit.swing
|
||||
|
||||
import com.jdbernard.pit.Category
|
||||
import com.jdbernard.pit.FileProject
|
||||
import com.jdbernard.pit.Issue
|
||||
import com.jdbernard.pit.Project
|
||||
import com.jdbernard.pit.Status
|
||||
import javax.swing.DefaultListModel
|
||||
import javax.swing.JOptionPane
|
||||
import javax.swing.SwingUtilities
|
||||
import javax.swing.tree.DefaultMutableTreeNode
|
||||
import javax.swing.tree.DefaultTreeModel
|
||||
|
||||
class PITController {
|
||||
|
||||
// these will be injected by Griffon
|
||||
def model
|
||||
def view
|
||||
|
||||
void mvcGroupInit(Map args) {
|
||||
|
||||
SwingUtilities.invokeAndWait {
|
||||
model.issueListRenderer = new IssueListCellRenderer()
|
||||
model.issueListRenderer.categoryIcons = view.categoryIcons
|
||||
model.issueListRenderer.statusIcons = view.statusIcons
|
||||
|
||||
def config = new File(System.getProperty('user.home'), '.pit')
|
||||
config = new File(config, 'pit_swing.groovy')
|
||||
|
||||
if (config.exists() && config.isFile()) {
|
||||
def loader = new GroovyClassLoader(PITController.classLoader)
|
||||
def configBinding = new Binding()
|
||||
|
||||
// add default values
|
||||
configBinding.templates = model.templates
|
||||
configBinding.issueListRenderer = model.issueListRenderer
|
||||
|
||||
def configScript = loader.parseClass(config)
|
||||
.newInstance(configBinding)
|
||||
|
||||
configScript.invokeMethod("run", null)
|
||||
|
||||
model.templates = configBinding.templates ?: [:]
|
||||
if (configBinding.issueListRenderer &&
|
||||
configBinding.issueListRenderer != model.issueListRenderer)
|
||||
model.issueListRenderer = configBinding.issueListRenderer
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
def action = { evt = null ->
|
||||
/**
|
||||
* displayProject
|
||||
* @param project Project to display.
|
||||
*
|
||||
*/
|
||||
void displayProject(Project project) {
|
||||
view.issueTextArea.text = ""
|
||||
if (!project) return
|
||||
|
||||
if (!model.projectListModels[(project.name)]) {
|
||||
def dlm = new DefaultListModel()
|
||||
project.eachIssue(model.filter) { dlm.addElement(it) }
|
||||
model.projectListModels[(project.name)] = dlm
|
||||
}
|
||||
|
||||
view.issueList.setModel(model.projectListModels[(project.name)])
|
||||
}
|
||||
|
||||
void displayIssue(Issue issue) {
|
||||
if (!issue) return
|
||||
view.issueTextArea.text = issue.text
|
||||
view.issueTextArea.caretPosition = 0
|
||||
}
|
||||
|
||||
void showProjectPopup(Project project, def x, def y) {
|
||||
model.popupProject = project
|
||||
view.projectPopupMenu.show(view.projectTree, x, y)
|
||||
}
|
||||
|
||||
void showIssuePopup(Issue issue, def x, def y) {
|
||||
model.popupIssue = issue
|
||||
view.issuePopupMenu.show(view.issueList, x, y)
|
||||
}
|
||||
|
||||
|
||||
def makeNodes(Project project) {
|
||||
def rootNode = new DefaultMutableTreeNode(project)
|
||||
project.eachProject(model.filter) { rootNode.add(makeNodes(it)) }
|
||||
return rootNode
|
||||
}
|
||||
|
||||
def newProject = { evt ->
|
||||
def name = JOptionPane.showInputDialog(view.frame, 'Project name:',
|
||||
'New Project...', JOptionPane.QUESTION_MESSAGE)
|
||||
|
||||
def project
|
||||
|
||||
if (evt.source == view.newProjectButton)
|
||||
project = model.selectedProject ?: model.rootProject
|
||||
else project = model.popupProject ?: model.rootProject
|
||||
def newProject = project.createNewProject(name)
|
||||
|
||||
project.projects[(newProject.name)] = newProject
|
||||
view.projectTree.model = new DefaultTreeModel(
|
||||
makeNodes(model.rootProject))
|
||||
}
|
||||
|
||||
def deleteProject = { evt ->
|
||||
def project
|
||||
|
||||
if (evt.source == view.deleteProjectButton)
|
||||
project = model.selectedProject ?: model.rootProject
|
||||
else project = model.popupProject ?: model.rootModel
|
||||
|
||||
project.delete()
|
||||
|
||||
model.rootProject = new FileProject(model.rootProject.source)
|
||||
}
|
||||
|
||||
def newIssue = { evt = null ->
|
||||
view.titleTextField.text = ""
|
||||
view.categoryComboBox.selectedItem = Category.BUG
|
||||
view.statusComboBox.selectedItem = Status.NEW
|
||||
view.prioritySpinner.setValue(5)
|
||||
view.newIssueDialog.visible = true
|
||||
}
|
||||
|
||||
def createIssue = { evt = null ->
|
||||
def issueText = ""
|
||||
|
||||
if (model.templates[(view.categoryComboBox.selectedItem)]) {
|
||||
issueText = model.templates[(view.categoryComboBox.selectedItem)]
|
||||
issueText = issueText.replaceFirst(/TITLE/,
|
||||
view.titleTextField.text)
|
||||
}
|
||||
|
||||
def issue = model.selectedProject.createNewIssue(
|
||||
category: view.categoryComboBox.selectedItem,
|
||||
status: view.statusComboBox.selectedItem,
|
||||
priority: view.prioritySpinner.value,
|
||||
text: issueText)
|
||||
model.projectListModels[(model.selectedProject.name)] = null
|
||||
displayProject(model.selectedProject)
|
||||
view.newIssueDialog.visible = false
|
||||
}
|
||||
|
||||
def deleteIssue = { evt ->
|
||||
def issue
|
||||
if (evt.source == view.deleteIssueButton)
|
||||
issue = view.issueList.selectedValue
|
||||
else issue = model.popupIssue
|
||||
|
||||
model.selectedProject.issues.remove(issue.id)
|
||||
model.projectListModels[(model.selectedProject.name)]
|
||||
.removeElement(issue)
|
||||
|
||||
issue.delete()
|
||||
}
|
||||
|
||||
def changeCategory = { evt ->
|
||||
model.popupIssue.status = status
|
||||
view.issueList.invalidate()
|
||||
view.issueList.repaint()
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@ -19,6 +19,16 @@
|
||||
|
||||
import groovy.swing.SwingBuilder
|
||||
import griffon.util.GriffonPlatformHelper
|
||||
import griffon.util.GriffonApplicationHelper
|
||||
|
||||
GriffonPlatformHelper.tweakForNativePlatform(app)
|
||||
SwingBuilder.lookAndFeel('gtk', 'mac', 'org.pushingpixels.substance.api.skin.SubstanceCremeCoffeeLookAndFeel', 'nimbus', ['metal', [boldFonts: false]])
|
||||
SwingBuilder.lookAndFeel('mac', 'org.pushingpixels.substance.api.skin.SubstanceCremeCoffeeLookAndFeel', 'nimbus', ['metal', [boldFonts: false]])
|
||||
|
||||
// make config directory
|
||||
def confDir = new File(System.getProperty('user.home'), '.pit')
|
||||
if (!confDir.exists()) confDir.mkdirs()
|
||||
// find or create configuration file
|
||||
def swingConf = new File(confDir, 'pit-swing.groovy')
|
||||
if (!swingConf.exists()) swingConf.createNewFile()
|
||||
// run config
|
||||
GriffonApplicationHelper.runScriptInsideEDT(swingConf.canonicalPath, app)
|
||||
|
@ -1,8 +1,34 @@
|
||||
package com.jdbernard.pit.swing
|
||||
|
||||
import com.jdbernard.pit.Category
|
||||
import com.jdbernard.pit.Filter
|
||||
import com.jdbernard.pit.Issue
|
||||
import com.jdbernard.pit.Project
|
||||
import com.jdbernard.pit.Status
|
||||
import groovy.beans.Bindable
|
||||
|
||||
class PITModel {
|
||||
@Bindable Project rootProject
|
||||
|
||||
// cache the ListModels
|
||||
def projectListModels = [:]
|
||||
|
||||
// filter for projects and issues
|
||||
Filter filter = new Filter(categories: [],
|
||||
status: [Status.NEW, Status.VALIDATION_REQUIRED])
|
||||
|
||||
@Bindable Project popupProject = null
|
||||
|
||||
@Bindable Project selectedProject = null
|
||||
|
||||
@Bindable Issue popupIssue = null
|
||||
|
||||
// configurable exntension points
|
||||
// ==============================
|
||||
|
||||
@Bindable def issueListRenderer
|
||||
|
||||
// map of category -> issue template
|
||||
def templates = [:]
|
||||
|
||||
}
|
||||
|
@ -27,87 +27,77 @@ import javax.swing.tree.TreeSelectionModel
|
||||
import net.miginfocom.swing.MigLayout
|
||||
|
||||
import java.awt.Color
|
||||
/* ********************
|
||||
* VIEW-Specific data
|
||||
* ********************/
|
||||
|
||||
// cache the ListModels
|
||||
projectListModels = [:]
|
||||
actions {
|
||||
action (
|
||||
id: 'newIssue',
|
||||
name: 'New Issue',
|
||||
icon: imageIcon("/add.png"),
|
||||
accelerator: shortcut('N'),
|
||||
closure: controller.newIssue,
|
||||
enabled: bind { model.selectedProject != null }
|
||||
)
|
||||
|
||||
// map of category -> list icon
|
||||
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 }
|
||||
)
|
||||
}
|
||||
|
||||
/* ****************
|
||||
* GUI components
|
||||
* ****************/
|
||||
categoryIcons = [:]
|
||||
|
||||
statusIcons = [:]
|
||||
|
||||
// filter for projects and issues
|
||||
filter = new Filter(categories: [],
|
||||
status: [Status.NEW, Status.VALIDATION_REQUIRED])
|
||||
|
||||
popupProject = null
|
||||
selectedProject = model.rootProject
|
||||
|
||||
popupIssue = null
|
||||
|
||||
// initialize category-related view data
|
||||
Category.values().each {
|
||||
categoryIcons[(it)] = imageIcon("/${it.name().toLowerCase()}.png")
|
||||
filter.categories.add(it)
|
||||
model.filter.categories.add(it)
|
||||
}
|
||||
|
||||
Status.values().each {
|
||||
statusIcons[(it)] = imageIcon("/${it.name().toLowerCase()}.png")
|
||||
}
|
||||
|
||||
/* ***************
|
||||
* event methods
|
||||
* ***************/
|
||||
|
||||
/**
|
||||
* displayProject
|
||||
* @param project Project to display.
|
||||
*
|
||||
*/
|
||||
displayProject = { project = null ->
|
||||
issueTextArea.text = ""
|
||||
if (!project) return
|
||||
|
||||
if (!projectListModels[(project.name)]) {
|
||||
def model = new DefaultListModel()
|
||||
project.eachIssue(filter) { model.addElement(it) }
|
||||
projectListModels[(project.name)] = model
|
||||
}
|
||||
|
||||
issueList.setModel(projectListModels[(project.name)])
|
||||
}
|
||||
|
||||
displayIssue = { issue = null ->
|
||||
if (issue) issueTextArea.text = issue.text
|
||||
}
|
||||
|
||||
showProjectPopup = { project, x, y ->
|
||||
popupProject = project
|
||||
projectPopupMenu[1].enabled = project != null
|
||||
projectPopupMenu.show(projectTree, x, y)
|
||||
}
|
||||
|
||||
showIssuePopup = { issue, x, y ->
|
||||
popupIssue = issue
|
||||
issuePopupMenu.eachWithIndex { menuItem, idx ->
|
||||
if (idx != 0) menuItem.enabled = issue != null }
|
||||
issuePopupMenu.show(issueList, x, y)
|
||||
}
|
||||
|
||||
newIssue = { evt = null ->
|
||||
titleTextField.text = ""
|
||||
categoryComboBox.selectedItem = Category.BUG
|
||||
statusComboBox.selectedItem = Status.NEW
|
||||
prioritySpinner.setValue(5)
|
||||
newIssueDialog.visible = true
|
||||
}
|
||||
|
||||
/* ****************
|
||||
* GUI components
|
||||
* ****************/
|
||||
openDialog = fileChooser(fileSelectionMode: JFileChooser.DIRECTORIES_ONLY)
|
||||
|
||||
newIssueDialog = dialog(title: 'New Task...', modal: true, pack: true,
|
||||
@ -149,64 +139,28 @@ newIssueDialog = dialog(title: 'New Task...', modal: true, pack: true,
|
||||
button('Cancel', actionPerformed: { newIssueDialog.visible = false },
|
||||
constraints: gbc(gridx: 0, gridy: 5, insets: [5, 5, 5, 5],
|
||||
anchor: GBC.EAST))
|
||||
button('Create Issue',
|
||||
button(createIssue,
|
||||
constraints: gbc(gridx: 1, gridy: 5, insets: [5, 5, 5, 5],
|
||||
anchor: GBC.WEST),
|
||||
actionPerformed: {
|
||||
def issue = selectedProject.createNewIssue(
|
||||
category: categoryComboBox.selectedItem,
|
||||
status: statusComboBox.selectedItem,
|
||||
priority: prioritySpinner.value,
|
||||
text: titleTextField.text)
|
||||
projectListModels[(selectedProject.name)] = null
|
||||
displayProject(selectedProject)
|
||||
newIssueDialog.visible = false
|
||||
})
|
||||
anchor: GBC.WEST))
|
||||
}
|
||||
|
||||
projectPopupMenu = popupMenu() {
|
||||
menuItem('New Project...', icon: imageIcon("/add.png"),
|
||||
actionPerformed: {
|
||||
def name = JOptionPane.showInputDialog(frame, 'Project name:',
|
||||
'New Project...', JOptionPane.QUESTION_MESSAGE)
|
||||
|
||||
if (!popupProject) popupProject = model.rootProject
|
||||
def newProject = popupProject.createNewProject(name)
|
||||
|
||||
popupProject.projects[(newProject.name)] = newProject
|
||||
projectTree.model = new DefaultTreeModel(
|
||||
makeNodes(model.rootProject))
|
||||
})
|
||||
menuItem('Delete Project', icon: imageIcon("/delete.png"),
|
||||
actionPerformed: {
|
||||
if (!popupProject) return
|
||||
popupProject.delete()
|
||||
// do not like, tied to Project implementation
|
||||
model.rootProject = new FileProject(model.rootProject.source)
|
||||
})
|
||||
menuItem(newProject)
|
||||
menuItem(deleteProjectPop)
|
||||
}
|
||||
|
||||
issuePopupMenu = popupMenu() {
|
||||
menuItem('New Issue...', icon: imageIcon("/add.png"),
|
||||
actionPerformed: newIssue)
|
||||
|
||||
menuItem('Delete Issue', icon: imageIcon("/delete.png"),
|
||||
actionPerformed: {
|
||||
if (!popupIssue) return
|
||||
selectedProject.issues.remove(popupIssue.id)
|
||||
projectListModels[(selectedProject.name)].removeElement(popupIssue)
|
||||
popupIssue.delete()
|
||||
})
|
||||
|
||||
menuItem(newIssue)
|
||||
menuItem(deleteIssuePop)
|
||||
separator()
|
||||
|
||||
menu('Change Category') {
|
||||
Category.values().each { category ->
|
||||
menuItem(category.toString(),
|
||||
icon: categoryIcons[(category)],
|
||||
enabled: bind { model.popupIssue != null },
|
||||
actionPerformed: {
|
||||
if (!popupIssue) return
|
||||
popupIssue.category = category
|
||||
model.popupIssue.category = category
|
||||
issueList.invalidate()
|
||||
issueList.repaint()
|
||||
})
|
||||
@ -217,9 +171,9 @@ issuePopupMenu = popupMenu() {
|
||||
Status.values().each { status ->
|
||||
menuItem(status.toString(),
|
||||
icon: statusIcons[(status)],
|
||||
enabled: bind { model.popupIssue != null },
|
||||
actionPerformed: {
|
||||
if (!popupIssue) return
|
||||
popupIssue.status = status
|
||||
model.popupIssue.status = status
|
||||
issueList.invalidate()
|
||||
issueList.repaint()
|
||||
})
|
||||
@ -227,12 +181,12 @@ issuePopupMenu = popupMenu() {
|
||||
}
|
||||
|
||||
menuItem('Change Priority...',
|
||||
enabled: bind { model.popupIssue != null },
|
||||
actionPerformed: {
|
||||
if (!popupIssue) return
|
||||
def newPriority = JOptionPane.showInputDialog(frame,
|
||||
'New priority (0-9)', 'Change Priority...',
|
||||
JOptionPane.QUESTION_MESSAGE)
|
||||
try { popupIssue.priority = newPriority.toInteger() }
|
||||
try { model.popupIssue.priority = newPriority.toInteger() }
|
||||
catch (exception) {
|
||||
JOptionPane.showMessage(frame, 'The priority value must ' +
|
||||
'be an integer in [0-9].', 'Change Priority...',
|
||||
@ -271,39 +225,37 @@ frame = application(title:'Personal Issue Tracker',
|
||||
|
||||
menu('View') {
|
||||
menu('Category') {
|
||||
Category.values().each {
|
||||
checkBoxMenuItem(it.toString(),
|
||||
selected: filter.categories.contains(it),
|
||||
Category.values().each { cat ->
|
||||
checkBoxMenuItem(cat.toString(),
|
||||
selected: model.filter.categories.contains(cat),
|
||||
actionPerformed: { evt ->
|
||||
def cat = Category.toCategory(evt.source.text)
|
||||
if (filter.categories.contains(cat)) {
|
||||
filter.categories.remove(cat)
|
||||
if (model.filter.categories.contains(cat)) {
|
||||
model.filter.categories.remove(cat)
|
||||
evt.source.selected = false
|
||||
} else {
|
||||
filter.categories.add(cat)
|
||||
model.filter.categories.add(cat)
|
||||
evt.source.selected = true
|
||||
}
|
||||
projectListModels.clear()
|
||||
displayProject(selectedProject)
|
||||
model.projectListModels.clear()
|
||||
controller.displayProject(model.selectedProject)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
menu('Status') {
|
||||
Status.values().each {
|
||||
checkBoxMenuItem(it.toString(),
|
||||
selected: filter.status.contains(it),
|
||||
Status.values().each { st ->
|
||||
checkBoxMenuItem(st.toString(),
|
||||
selected: model.filter.status.contains(st),
|
||||
actionPerformed: { evt ->
|
||||
def st = Status.toStatus(evt.source.text[0..5])
|
||||
if (filter.status.contains(st)) {
|
||||
filter.status.remove(st)
|
||||
if (model.filter.status.contains(st)) {
|
||||
model.filter.status.remove(st)
|
||||
evt.source.selected = false
|
||||
} else {
|
||||
filter.status.add(st)
|
||||
model.filter.status.add(st)
|
||||
evt.source.selected = true
|
||||
}
|
||||
projectListModels.clear()
|
||||
displayProject(selectedProject)
|
||||
model.projectListModels.clear()
|
||||
controller.displayProject(model.selectedProject)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -315,37 +267,37 @@ frame = application(title:'Personal Issue Tracker',
|
||||
checkBoxMenuItem('By ID',
|
||||
buttonGroup: sortMenuButtonGroup,
|
||||
actionPerformed: {
|
||||
filter.issueSorter = { it.id }
|
||||
projectListModels.clear()
|
||||
displayProject(selectedProject)
|
||||
model.filter.issueSorter = { it.id }
|
||||
model.projectListModels.clear()
|
||||
controller.displayProject(selectedProject)
|
||||
})
|
||||
checkBoxMenuItem('By Category',
|
||||
buttonGroup: sortMenuButtonGroup,
|
||||
actionPerformed: {
|
||||
filter.issueSorter = { it.category }
|
||||
projectListModels.clear()
|
||||
displayProject(selectedProject)
|
||||
model.filter.issueSorter = { it.category }
|
||||
model.projectListModels.clear()
|
||||
controller.displayProject(selectedProject)
|
||||
})
|
||||
checkBoxMenuItem('By Status',
|
||||
buttonGroup: sortMenuButtonGroup,
|
||||
actionPerformed: {
|
||||
filter.issueSorter = { it.status }
|
||||
projectListModels.clear()
|
||||
displayProject(selectedProject)
|
||||
model.filter.issueSorter = { it.status }
|
||||
model.projectListModels.clear()
|
||||
controller.displayProject(selectedProject)
|
||||
})
|
||||
checkBoxMenuItem('By Priority',
|
||||
buttonGroup: sortMenuButtonGroup,
|
||||
actionPerformed: {
|
||||
filter.issueSorter = { it.priority }
|
||||
projectListModels.clear()
|
||||
displayProject(selectedProject)
|
||||
model.filter.issueSorter = { it.priority }
|
||||
model.projectListModels.clear()
|
||||
controller.displayProject(selectedProject)
|
||||
})
|
||||
checkBoxMenuItem('By Title',
|
||||
buttonGroup: sortMenuButtonGroup,
|
||||
actionPerformed: {
|
||||
filter.issueSorter = { it.title }
|
||||
projectListModels.clear()
|
||||
displayProject(selectedProject)
|
||||
model.filter.issueSorter = { it.title }
|
||||
model.projectListModels.clear()
|
||||
controller.displayProject(selectedProject)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -374,21 +326,21 @@ frame = application(title:'Personal Issue Tracker',
|
||||
if (model.rootProject) {
|
||||
projectTree.rootVisible =
|
||||
model.rootProject.issues.size()
|
||||
new DefaultTreeModel(makeNodes(model.rootProject))
|
||||
new DefaultTreeModel(controller.makeNodes(
|
||||
model.rootProject))
|
||||
} else {
|
||||
projectTree.rootVisible = false
|
||||
new DefaultTreeModel(new DefaultMutableTreeNode())
|
||||
}
|
||||
}),
|
||||
valueChanged: { evt ->
|
||||
selectedProject = evt?.newLeadSelectionPath?.
|
||||
model.selectedProject = evt?.newLeadSelectionPath?.
|
||||
lastPathComponent?.userObject ?: model.rootProject
|
||||
displayProject(selectedProject)
|
||||
//deleteProjectButton.enabled = selectedProject != null
|
||||
controller.displayProject(model.selectedProject)
|
||||
},
|
||||
mouseClicked: { evt ->
|
||||
if (evt.button == MouseEvent.BUTTON3) {
|
||||
showProjectPopup(
|
||||
controller.showProjectPopup(
|
||||
projectTree.getPathForLocation(evt.x, evt.y)
|
||||
?.lastPathComponent?.userObject,
|
||||
evt.x, evt.y)
|
||||
@ -403,35 +355,12 @@ frame = application(title:'Personal Issue Tracker',
|
||||
}
|
||||
|
||||
// project buttons
|
||||
button('New Project', icon: imageIcon("/add.png"),
|
||||
newProjectButton = button(newProject,
|
||||
constraints: gbc(fill: GBC.NONE, gridx: 0, gridy: 1,
|
||||
anchor: GBC.WEST),
|
||||
actionPerformed: {
|
||||
def name = JOptionPane.showInputDialog(frame,
|
||||
'Project name:', 'New Project...',
|
||||
JOptionPane.QUESTION_MESSAGE)
|
||||
|
||||
if (!selectedProject) selectedProject = model.rootProject
|
||||
def newProject = selectedProject.createNewProject(name)
|
||||
|
||||
selectedProject.projects[(newProject.name)] = newProject
|
||||
projectTree.model = new DefaultTreeModel(
|
||||
makeNodes(model.rootProject))
|
||||
|
||||
})
|
||||
deleteProjectButton = button('Delete Project',
|
||||
icon: imageIcon("/delete.png"),
|
||||
anchor: GBC.WEST))
|
||||
deleteProjectButton = button(deleteProject,
|
||||
constraints: gbc(fill: GBC.NONE, gridx: 1, gridy: 1,
|
||||
anchor: GBC.WEST),
|
||||
enabled: bind(source: projectTree, sourceEvent: 'valueChanged',
|
||||
sourceValue: { projectTree?.lastSelectedPathComponent != null}),
|
||||
actionPerformed: {
|
||||
if (!selectedProject) return
|
||||
selectedProject.delete()
|
||||
// do not like, tied to Project implementation
|
||||
model.rootProject = new FileProject(
|
||||
model.rootProject.source)
|
||||
})
|
||||
anchor: GBC.WEST))
|
||||
}
|
||||
|
||||
// split between issue list and issue details
|
||||
@ -445,42 +374,33 @@ frame = application(title:'Personal Issue Tracker',
|
||||
weighty: 2, gridx: 0, gridy: 0, gridwidth: 3)) {
|
||||
|
||||
issueList = list(
|
||||
cellRenderer: new IssueListCellRenderer(
|
||||
categoryIcons: categoryIcons,
|
||||
statusIcons: statusIcons),
|
||||
cellRenderer: bind(source: model,
|
||||
sourceProperty: 'issueListRenderer'),
|
||||
selectionMode: ListSelectionModel.SINGLE_SELECTION,
|
||||
valueChanged: { displayIssue(issueList.selectedValue) },
|
||||
valueChanged: { evt ->
|
||||
controller.displayIssue(issueList.selectedValue)
|
||||
},
|
||||
mouseClicked: { evt ->
|
||||
if (evt.button == MouseEvent.BUTTON3) {
|
||||
issueList.selectedIndex = issueList.locationToIndex(
|
||||
[evt.x, evt.y] as Point)
|
||||
|
||||
showIssuePopup(issueList.selectedValue,
|
||||
evt.x, evt.y)
|
||||
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('New Issue',
|
||||
constraints: gbc(gridx: 1, gridy: 1, anchor: GBC.EAST),
|
||||
icon: imageIcon("/add.png"), actionPerformed: newIssue)
|
||||
anchor: GBC.WEST), selected: true)
|
||||
button(newIssue,
|
||||
constraints: gbc(gridx: 1, gridy: 1, anchor: GBC.EAST))
|
||||
|
||||
deleteIssueButton = button('Delete Issue',
|
||||
deleteIssueButton = button(deleteIssue,
|
||||
constraints: gbc(gridx: 2, gridy: 1, anchor: GBC.EAST),
|
||||
enabled: bind(source: issueList, sourceEvent: 'valueChanged',
|
||||
sourceValue: { issueList.selectedValue != null }),
|
||||
icon: imageIcon("/delete.png"),
|
||||
actionPerformed: {
|
||||
if (!issueList?.selectedIssue) return
|
||||
selectedProject.issues.remove(issueList.selectedValue)
|
||||
projectListModels[(selectedProject.name)]
|
||||
.removeElement(issueList.selectedValue)
|
||||
issueList.selectedIssue.delete()
|
||||
})
|
||||
sourceValue: { issueList.selectedValue != null }))
|
||||
|
||||
}
|
||||
scrollPane(constraints: "bottom") {
|
||||
@ -488,6 +408,8 @@ frame = application(title:'Personal Issue Tracker',
|
||||
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: {
|
||||
@ -504,12 +426,3 @@ frame = application(title:'Personal Issue Tracker',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ******************
|
||||
* Auxilary methods
|
||||
* ******************/
|
||||
def makeNodes(Project project) {
|
||||
def rootNode = new DefaultMutableTreeNode(project)
|
||||
project.eachProject(filter) { rootNode.add(makeNodes(it)) }
|
||||
return rootNode
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user