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 Status status
|
||||||
protected int priority
|
protected int priority
|
||||||
protected String text
|
protected String text
|
||||||
|
protected Date deliveryDate
|
||||||
|
protected Date creationDate
|
||||||
|
|
||||||
Issue(String id, Category c = Category.TASK, Status s = Status.NEW,
|
Issue(String id, Category c = Category.TASK, Status s = Status.NEW,
|
||||||
int p = 9) {
|
int p = 9) {
|
||||||
@ -16,6 +18,8 @@ public abstract class Issue {
|
|||||||
this.category = c
|
this.category = c
|
||||||
this.status = s
|
this.status = s
|
||||||
this.priority = p
|
this.priority = p
|
||||||
|
this.creationDate = new Date()
|
||||||
|
this.deliveryDate = null
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() { return id; }
|
public String getId() { return id; }
|
||||||
@ -48,6 +52,14 @@ public abstract class Issue {
|
|||||||
|
|
||||||
public void setText(String t) { text = t }
|
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
|
@Override
|
||||||
public String toString() { return "${id}(${priority}-${status}): ${category} ${title}" }
|
public String toString() { return "${id}(${priority}-${status}): ${category} ${title}" }
|
||||||
|
|
||||||
|
@ -1,17 +1,166 @@
|
|||||||
package com.jdbernard.pit.swing
|
package com.jdbernard.pit.swing
|
||||||
|
|
||||||
|
import com.jdbernard.pit.Category
|
||||||
import com.jdbernard.pit.FileProject
|
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 {
|
class PITController {
|
||||||
|
|
||||||
// these will be injected by Griffon
|
// these will be injected by Griffon
|
||||||
def model
|
def model
|
||||||
def view
|
def view
|
||||||
|
|
||||||
void mvcGroupInit(Map args) {
|
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 groovy.swing.SwingBuilder
|
||||||
import griffon.util.GriffonPlatformHelper
|
import griffon.util.GriffonPlatformHelper
|
||||||
|
import griffon.util.GriffonApplicationHelper
|
||||||
|
|
||||||
GriffonPlatformHelper.tweakForNativePlatform(app)
|
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
|
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.Project
|
||||||
|
import com.jdbernard.pit.Status
|
||||||
import groovy.beans.Bindable
|
import groovy.beans.Bindable
|
||||||
|
|
||||||
class PITModel {
|
class PITModel {
|
||||||
@Bindable Project rootProject
|
@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 net.miginfocom.swing.MigLayout
|
||||||
|
|
||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
/* ********************
|
|
||||||
* VIEW-Specific data
|
|
||||||
* ********************/
|
|
||||||
|
|
||||||
// cache the ListModels
|
actions {
|
||||||
projectListModels = [:]
|
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 = [:]
|
categoryIcons = [:]
|
||||||
|
|
||||||
statusIcons = [:]
|
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
|
// initialize category-related view data
|
||||||
Category.values().each {
|
Category.values().each {
|
||||||
categoryIcons[(it)] = imageIcon("/${it.name().toLowerCase()}.png")
|
categoryIcons[(it)] = imageIcon("/${it.name().toLowerCase()}.png")
|
||||||
filter.categories.add(it)
|
model.filter.categories.add(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
Status.values().each {
|
Status.values().each {
|
||||||
statusIcons[(it)] = imageIcon("/${it.name().toLowerCase()}.png")
|
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)
|
openDialog = fileChooser(fileSelectionMode: JFileChooser.DIRECTORIES_ONLY)
|
||||||
|
|
||||||
newIssueDialog = dialog(title: 'New Task...', modal: true, pack: true,
|
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 },
|
button('Cancel', actionPerformed: { newIssueDialog.visible = false },
|
||||||
constraints: gbc(gridx: 0, gridy: 5, insets: [5, 5, 5, 5],
|
constraints: gbc(gridx: 0, gridy: 5, insets: [5, 5, 5, 5],
|
||||||
anchor: GBC.EAST))
|
anchor: GBC.EAST))
|
||||||
button('Create Issue',
|
button(createIssue,
|
||||||
constraints: gbc(gridx: 1, gridy: 5, insets: [5, 5, 5, 5],
|
constraints: gbc(gridx: 1, gridy: 5, insets: [5, 5, 5, 5],
|
||||||
anchor: GBC.WEST),
|
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
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
projectPopupMenu = popupMenu() {
|
projectPopupMenu = popupMenu() {
|
||||||
menuItem('New Project...', icon: imageIcon("/add.png"),
|
menuItem(newProject)
|
||||||
actionPerformed: {
|
menuItem(deleteProjectPop)
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
issuePopupMenu = popupMenu() {
|
issuePopupMenu = popupMenu() {
|
||||||
menuItem('New Issue...', icon: imageIcon("/add.png"),
|
menuItem(newIssue)
|
||||||
actionPerformed: newIssue)
|
menuItem(deleteIssuePop)
|
||||||
|
|
||||||
menuItem('Delete Issue', icon: imageIcon("/delete.png"),
|
|
||||||
actionPerformed: {
|
|
||||||
if (!popupIssue) return
|
|
||||||
selectedProject.issues.remove(popupIssue.id)
|
|
||||||
projectListModels[(selectedProject.name)].removeElement(popupIssue)
|
|
||||||
popupIssue.delete()
|
|
||||||
})
|
|
||||||
|
|
||||||
separator()
|
separator()
|
||||||
|
|
||||||
menu('Change Category') {
|
menu('Change Category') {
|
||||||
Category.values().each { category ->
|
Category.values().each { category ->
|
||||||
menuItem(category.toString(),
|
menuItem(category.toString(),
|
||||||
icon: categoryIcons[(category)],
|
icon: categoryIcons[(category)],
|
||||||
|
enabled: bind { model.popupIssue != null },
|
||||||
actionPerformed: {
|
actionPerformed: {
|
||||||
if (!popupIssue) return
|
model.popupIssue.category = category
|
||||||
popupIssue.category = category
|
|
||||||
issueList.invalidate()
|
issueList.invalidate()
|
||||||
issueList.repaint()
|
issueList.repaint()
|
||||||
})
|
})
|
||||||
@ -217,9 +171,9 @@ issuePopupMenu = popupMenu() {
|
|||||||
Status.values().each { status ->
|
Status.values().each { status ->
|
||||||
menuItem(status.toString(),
|
menuItem(status.toString(),
|
||||||
icon: statusIcons[(status)],
|
icon: statusIcons[(status)],
|
||||||
|
enabled: bind { model.popupIssue != null },
|
||||||
actionPerformed: {
|
actionPerformed: {
|
||||||
if (!popupIssue) return
|
model.popupIssue.status = status
|
||||||
popupIssue.status = status
|
|
||||||
issueList.invalidate()
|
issueList.invalidate()
|
||||||
issueList.repaint()
|
issueList.repaint()
|
||||||
})
|
})
|
||||||
@ -227,12 +181,12 @@ issuePopupMenu = popupMenu() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
menuItem('Change Priority...',
|
menuItem('Change Priority...',
|
||||||
|
enabled: bind { model.popupIssue != null },
|
||||||
actionPerformed: {
|
actionPerformed: {
|
||||||
if (!popupIssue) return
|
|
||||||
def newPriority = JOptionPane.showInputDialog(frame,
|
def newPriority = JOptionPane.showInputDialog(frame,
|
||||||
'New priority (0-9)', 'Change Priority...',
|
'New priority (0-9)', 'Change Priority...',
|
||||||
JOptionPane.QUESTION_MESSAGE)
|
JOptionPane.QUESTION_MESSAGE)
|
||||||
try { popupIssue.priority = newPriority.toInteger() }
|
try { model.popupIssue.priority = newPriority.toInteger() }
|
||||||
catch (exception) {
|
catch (exception) {
|
||||||
JOptionPane.showMessage(frame, 'The priority value must ' +
|
JOptionPane.showMessage(frame, 'The priority value must ' +
|
||||||
'be an integer in [0-9].', 'Change Priority...',
|
'be an integer in [0-9].', 'Change Priority...',
|
||||||
@ -271,39 +225,37 @@ frame = application(title:'Personal Issue Tracker',
|
|||||||
|
|
||||||
menu('View') {
|
menu('View') {
|
||||||
menu('Category') {
|
menu('Category') {
|
||||||
Category.values().each {
|
Category.values().each { cat ->
|
||||||
checkBoxMenuItem(it.toString(),
|
checkBoxMenuItem(cat.toString(),
|
||||||
selected: filter.categories.contains(it),
|
selected: model.filter.categories.contains(cat),
|
||||||
actionPerformed: { evt ->
|
actionPerformed: { evt ->
|
||||||
def cat = Category.toCategory(evt.source.text)
|
if (model.filter.categories.contains(cat)) {
|
||||||
if (filter.categories.contains(cat)) {
|
model.filter.categories.remove(cat)
|
||||||
filter.categories.remove(cat)
|
|
||||||
evt.source.selected = false
|
evt.source.selected = false
|
||||||
} else {
|
} else {
|
||||||
filter.categories.add(cat)
|
model.filter.categories.add(cat)
|
||||||
evt.source.selected = true
|
evt.source.selected = true
|
||||||
}
|
}
|
||||||
projectListModels.clear()
|
model.projectListModels.clear()
|
||||||
displayProject(selectedProject)
|
controller.displayProject(model.selectedProject)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
menu('Status') {
|
menu('Status') {
|
||||||
Status.values().each {
|
Status.values().each { st ->
|
||||||
checkBoxMenuItem(it.toString(),
|
checkBoxMenuItem(st.toString(),
|
||||||
selected: filter.status.contains(it),
|
selected: model.filter.status.contains(st),
|
||||||
actionPerformed: { evt ->
|
actionPerformed: { evt ->
|
||||||
def st = Status.toStatus(evt.source.text[0..5])
|
if (model.filter.status.contains(st)) {
|
||||||
if (filter.status.contains(st)) {
|
model.filter.status.remove(st)
|
||||||
filter.status.remove(st)
|
|
||||||
evt.source.selected = false
|
evt.source.selected = false
|
||||||
} else {
|
} else {
|
||||||
filter.status.add(st)
|
model.filter.status.add(st)
|
||||||
evt.source.selected = true
|
evt.source.selected = true
|
||||||
}
|
}
|
||||||
projectListModels.clear()
|
model.projectListModels.clear()
|
||||||
displayProject(selectedProject)
|
controller.displayProject(model.selectedProject)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -315,37 +267,37 @@ frame = application(title:'Personal Issue Tracker',
|
|||||||
checkBoxMenuItem('By ID',
|
checkBoxMenuItem('By ID',
|
||||||
buttonGroup: sortMenuButtonGroup,
|
buttonGroup: sortMenuButtonGroup,
|
||||||
actionPerformed: {
|
actionPerformed: {
|
||||||
filter.issueSorter = { it.id }
|
model.filter.issueSorter = { it.id }
|
||||||
projectListModels.clear()
|
model.projectListModels.clear()
|
||||||
displayProject(selectedProject)
|
controller.displayProject(selectedProject)
|
||||||
})
|
})
|
||||||
checkBoxMenuItem('By Category',
|
checkBoxMenuItem('By Category',
|
||||||
buttonGroup: sortMenuButtonGroup,
|
buttonGroup: sortMenuButtonGroup,
|
||||||
actionPerformed: {
|
actionPerformed: {
|
||||||
filter.issueSorter = { it.category }
|
model.filter.issueSorter = { it.category }
|
||||||
projectListModels.clear()
|
model.projectListModels.clear()
|
||||||
displayProject(selectedProject)
|
controller.displayProject(selectedProject)
|
||||||
})
|
})
|
||||||
checkBoxMenuItem('By Status',
|
checkBoxMenuItem('By Status',
|
||||||
buttonGroup: sortMenuButtonGroup,
|
buttonGroup: sortMenuButtonGroup,
|
||||||
actionPerformed: {
|
actionPerformed: {
|
||||||
filter.issueSorter = { it.status }
|
model.filter.issueSorter = { it.status }
|
||||||
projectListModels.clear()
|
model.projectListModels.clear()
|
||||||
displayProject(selectedProject)
|
controller.displayProject(selectedProject)
|
||||||
})
|
})
|
||||||
checkBoxMenuItem('By Priority',
|
checkBoxMenuItem('By Priority',
|
||||||
buttonGroup: sortMenuButtonGroup,
|
buttonGroup: sortMenuButtonGroup,
|
||||||
actionPerformed: {
|
actionPerformed: {
|
||||||
filter.issueSorter = { it.priority }
|
model.filter.issueSorter = { it.priority }
|
||||||
projectListModels.clear()
|
model.projectListModels.clear()
|
||||||
displayProject(selectedProject)
|
controller.displayProject(selectedProject)
|
||||||
})
|
})
|
||||||
checkBoxMenuItem('By Title',
|
checkBoxMenuItem('By Title',
|
||||||
buttonGroup: sortMenuButtonGroup,
|
buttonGroup: sortMenuButtonGroup,
|
||||||
actionPerformed: {
|
actionPerformed: {
|
||||||
filter.issueSorter = { it.title }
|
model.filter.issueSorter = { it.title }
|
||||||
projectListModels.clear()
|
model.projectListModels.clear()
|
||||||
displayProject(selectedProject)
|
controller.displayProject(selectedProject)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -374,21 +326,21 @@ frame = application(title:'Personal Issue Tracker',
|
|||||||
if (model.rootProject) {
|
if (model.rootProject) {
|
||||||
projectTree.rootVisible =
|
projectTree.rootVisible =
|
||||||
model.rootProject.issues.size()
|
model.rootProject.issues.size()
|
||||||
new DefaultTreeModel(makeNodes(model.rootProject))
|
new DefaultTreeModel(controller.makeNodes(
|
||||||
|
model.rootProject))
|
||||||
} else {
|
} else {
|
||||||
projectTree.rootVisible = false
|
projectTree.rootVisible = false
|
||||||
new DefaultTreeModel(new DefaultMutableTreeNode())
|
new DefaultTreeModel(new DefaultMutableTreeNode())
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
valueChanged: { evt ->
|
valueChanged: { evt ->
|
||||||
selectedProject = evt?.newLeadSelectionPath?.
|
model.selectedProject = evt?.newLeadSelectionPath?.
|
||||||
lastPathComponent?.userObject ?: model.rootProject
|
lastPathComponent?.userObject ?: model.rootProject
|
||||||
displayProject(selectedProject)
|
controller.displayProject(model.selectedProject)
|
||||||
//deleteProjectButton.enabled = selectedProject != null
|
|
||||||
},
|
},
|
||||||
mouseClicked: { evt ->
|
mouseClicked: { evt ->
|
||||||
if (evt.button == MouseEvent.BUTTON3) {
|
if (evt.button == MouseEvent.BUTTON3) {
|
||||||
showProjectPopup(
|
controller.showProjectPopup(
|
||||||
projectTree.getPathForLocation(evt.x, evt.y)
|
projectTree.getPathForLocation(evt.x, evt.y)
|
||||||
?.lastPathComponent?.userObject,
|
?.lastPathComponent?.userObject,
|
||||||
evt.x, evt.y)
|
evt.x, evt.y)
|
||||||
@ -403,35 +355,12 @@ frame = application(title:'Personal Issue Tracker',
|
|||||||
}
|
}
|
||||||
|
|
||||||
// project buttons
|
// project buttons
|
||||||
button('New Project', icon: imageIcon("/add.png"),
|
newProjectButton = button(newProject,
|
||||||
constraints: gbc(fill: GBC.NONE, gridx: 0, gridy: 1,
|
constraints: gbc(fill: GBC.NONE, gridx: 0, gridy: 1,
|
||||||
anchor: GBC.WEST),
|
anchor: GBC.WEST))
|
||||||
actionPerformed: {
|
deleteProjectButton = button(deleteProject,
|
||||||
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"),
|
|
||||||
constraints: gbc(fill: GBC.NONE, gridx: 1, gridy: 1,
|
constraints: gbc(fill: GBC.NONE, gridx: 1, gridy: 1,
|
||||||
anchor: GBC.WEST),
|
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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// split between issue list and issue details
|
// 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)) {
|
weighty: 2, gridx: 0, gridy: 0, gridwidth: 3)) {
|
||||||
|
|
||||||
issueList = list(
|
issueList = list(
|
||||||
cellRenderer: new IssueListCellRenderer(
|
cellRenderer: bind(source: model,
|
||||||
categoryIcons: categoryIcons,
|
sourceProperty: 'issueListRenderer'),
|
||||||
statusIcons: statusIcons),
|
|
||||||
selectionMode: ListSelectionModel.SINGLE_SELECTION,
|
selectionMode: ListSelectionModel.SINGLE_SELECTION,
|
||||||
valueChanged: { displayIssue(issueList.selectedValue) },
|
valueChanged: { evt ->
|
||||||
|
controller.displayIssue(issueList.selectedValue)
|
||||||
|
},
|
||||||
mouseClicked: { evt ->
|
mouseClicked: { evt ->
|
||||||
if (evt.button == MouseEvent.BUTTON3) {
|
if (evt.button == MouseEvent.BUTTON3) {
|
||||||
issueList.selectedIndex = issueList.locationToIndex(
|
issueList.selectedIndex = issueList.locationToIndex(
|
||||||
[evt.x, evt.y] as Point)
|
[evt.x, evt.y] as Point)
|
||||||
|
|
||||||
showIssuePopup(issueList.selectedValue,
|
controller.showIssuePopup(
|
||||||
evt.x, evt.y)
|
issueList.selectedValue, evt.x, evt.y)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
wordWrapCheckBox = checkBox('Word wrap',
|
wordWrapCheckBox = checkBox('Word wrap',
|
||||||
constraints: gbc(gridx: 0, gridy: 1, weightx: 2,
|
constraints: gbc(gridx: 0, gridy: 1, weightx: 2,
|
||||||
anchor: GBC.WEST),
|
anchor: GBC.WEST), selected: true)
|
||||||
selected: true)
|
button(newIssue,
|
||||||
button('New Issue',
|
constraints: gbc(gridx: 1, gridy: 1, anchor: GBC.EAST))
|
||||||
constraints: gbc(gridx: 1, gridy: 1, anchor: GBC.EAST),
|
|
||||||
icon: imageIcon("/add.png"), actionPerformed: newIssue)
|
|
||||||
|
|
||||||
deleteIssueButton = button('Delete Issue',
|
deleteIssueButton = button(deleteIssue,
|
||||||
constraints: gbc(gridx: 2, gridy: 1, anchor: GBC.EAST),
|
constraints: gbc(gridx: 2, gridy: 1, anchor: GBC.EAST),
|
||||||
enabled: bind(source: issueList, sourceEvent: 'valueChanged',
|
enabled: bind(source: issueList, sourceEvent: 'valueChanged',
|
||||||
sourceValue: { issueList.selectedValue != null }),
|
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()
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
scrollPane(constraints: "bottom") {
|
scrollPane(constraints: "bottom") {
|
||||||
@ -488,6 +408,8 @@ frame = application(title:'Personal Issue Tracker',
|
|||||||
wrapStyleWord: true,
|
wrapStyleWord: true,
|
||||||
lineWrap: bind(source: wordWrapCheckBox,
|
lineWrap: bind(source: wordWrapCheckBox,
|
||||||
sourceProperty: 'selected'),
|
sourceProperty: 'selected'),
|
||||||
|
editable: bind( source: issueList, sourceEvent: 'valueChanged',
|
||||||
|
sourceValue: { issueList.selectedValue != null }),
|
||||||
font: new Font(Font.MONOSPACED, Font.PLAIN, 10),
|
font: new Font(Font.MONOSPACED, Font.PLAIN, 10),
|
||||||
focusGained: {},
|
focusGained: {},
|
||||||
focusLost: {
|
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