HG corrupted local copy, lost 22 changesets

This commit is contained in:
Jonathan Bernard 2010-02-26 13:21:24 -06:00
parent 39d98f7dde
commit 4179b993f4
80 changed files with 769 additions and 111 deletions

View File

@ -0,0 +1,2 @@
Fix Issue.toString() so that only the first letter is capitalized.
==================================================================

33
issues/libpit/0021fs.rst Normal file
View File

@ -0,0 +1,33 @@
Divorce opened/closed status and classification.
================================================
We need to track open/closed status and classification independantly.
Classification is one category, status should be another. Possible values for
status:
* new
* resolved
* awaiting validation
* rejected
* reassigned
For the file-based issue implementation, the file format changes from
``nnnnsp`` to ``nnnncsp`` where:
* ``nnnn`` still represents the bug number (no change)
* ``c`` represents the *category* (used to be ``s`` the status):
* ``b``: Bug
* ``f``: Feature
* ``t``: Task
* ``s`` represents *status*, a new field:
* ``a``: Reassigned
* ``j``: Rejected
* ``n``: New
* ``s``: Resolved
* ``v``: Awaiting Validation
* ``p`` still represents priority, ``0`` being the highest and ``9`` the
lowest.

View File

@ -0,0 +1,5 @@
Remember last opened directory.
===============================
This affects the JFileChooser, not the model.rootProject.

View File

@ -0,0 +1,2 @@
De-select the 'CLOSED' category by default.
===========================================

View File

@ -0,0 +1,2 @@
Clear the 'New Task...' dialog when hidden.
===========================================

View File

@ -0,0 +1,2 @@
Set the default priority to '5'.
================================

View File

@ -0,0 +1,2 @@
Issue display needs to save changes.
====================================

View File

@ -0,0 +1,2 @@
Do not load project directory on startup.
=========================================

View File

@ -0,0 +1,2 @@
Clear project lists when opening a new directory.
=================================================

View File

@ -0,0 +1,5 @@
Issue display may still lose changes.
=====================================
If the mouse is not within the text area and the user clicks on something
that changes the text area content, the changes are not saved.

View File

@ -0,0 +1 @@
Add an optional word-wrap at 80 characters for the Issue display

View File

@ -0,0 +1,2 @@
Make 'New' the default new issue status.
========================================

View File

@ -0,0 +1,2 @@
Add the ability to sort issues based on priority, id, category, or status.
==========================================================================

View File

@ -1,10 +1,10 @@
#Thu Feb 25 17:24:06 CST 2010
#Fri Feb 26 11:43:12 CST 2010
build.dir=build
src.dir=src
lib.shared.dir=../shared-libs
test.dir=test
build.number=1
expected.application.version=1.1.8
build.number=2
expected.application.version=2.0.1
lib.dir=lib
release.dir=release
release.jar=pit-${application.version}.jar

Binary file not shown.

Binary file not shown.

View File

@ -46,6 +46,8 @@ badd +1 src/com/jdbernard/pit/FileIssue.groovy
badd +1 test/com/jdbernard/pit/FileIssueTest.groovy
badd +1 src/com/jdbernard/pit/FileProject.groovy
badd +1 test/com/jdbernard/pit/FileProjectTest.groovy
badd +1 src/com/jdbernard/pit/Status.groovy
badd +0 test/com/jdbernard/pit/StatusTest.groovy
args build.xml
edit build.xml
set splitbelow splitright
@ -155,7 +157,7 @@ setlocal nowinfixwidth
setlocal wrap
setlocal wrapmargin=0
silent! normal! zE
let s:l = 1 - ((0 * winheight(0) + 39) / 78)
let s:l = 1 - ((0 * winheight(0) + 28) / 57)
if s:l < 1 | let s:l = 1 | endif
exe s:l
normal! zt
@ -259,7 +261,7 @@ setlocal nowinfixwidth
setlocal wrap
setlocal wrapmargin=0
silent! normal! zE
let s:l = 1 - ((0 * winheight(0) + 39) / 78)
let s:l = 1 - ((0 * winheight(0) + 28) / 57)
if s:l < 1 | let s:l = 1 | endif
exe s:l
normal! zt
@ -376,7 +378,7 @@ setlocal nowinfixwidth
setlocal wrap
setlocal wrapmargin=0
silent! normal! zE
let s:l = 1 - ((0 * winheight(0) + 39) / 78)
let s:l = 1 - ((0 * winheight(0) + 28) / 57)
if s:l < 1 | let s:l = 1 | endif
exe s:l
normal! zt
@ -480,7 +482,228 @@ setlocal nowinfixwidth
setlocal wrap
setlocal wrapmargin=0
silent! normal! zE
let s:l = 1 - ((0 * winheight(0) + 39) / 78)
let s:l = 1 - ((0 * winheight(0) + 28) / 57)
if s:l < 1 | let s:l = 1 | endif
exe s:l
normal! zt
1
normal! 0
wincmd w
exe 'vert 1resize ' . ((&columns * 91 + 91) / 182)
exe 'vert 2resize ' . ((&columns * 90 + 91) / 182)
tabedit src/com/jdbernard/pit/Status.groovy
set splitbelow splitright
wincmd _ | wincmd |
vsplit
1wincmd h
wincmd w
set nosplitbelow
set nosplitright
wincmd t
set winheight=1 winwidth=1
exe 'vert 1resize ' . ((&columns * 91 + 91) / 182)
exe 'vert 2resize ' . ((&columns * 90 + 91) / 182)
argglobal
setlocal keymap=
setlocal noarabic
setlocal autoindent
setlocal balloonexpr=
setlocal nobinary
setlocal bufhidden=
setlocal buflisted
setlocal buftype=
setlocal nocindent
setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
setlocal cinoptions=
setlocal cinwords=if,else,while,do,for,switch
setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
setlocal commentstring=/*%s*/
setlocal complete=.,w,b,u,t,i
setlocal completefunc=
setlocal nocopyindent
setlocal nocursorcolumn
setlocal nocursorline
setlocal define=
setlocal dictionary=
setlocal nodiff
setlocal equalprg=
setlocal errorformat=
setlocal expandtab
if &filetype != 'groovy'
setlocal filetype=groovy
endif
setlocal foldcolumn=0
setlocal foldenable
setlocal foldexpr=0
setlocal foldignore=#
setlocal foldlevel=0
setlocal foldmarker={{{,}}}
setlocal foldmethod=manual
setlocal foldminlines=1
setlocal foldnestmax=20
setlocal foldtext=foldtext()
setlocal formatexpr=
setlocal formatoptions=tcq
setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
setlocal grepprg=
setlocal iminsert=2
setlocal imsearch=2
setlocal include=
setlocal includeexpr=
setlocal indentexpr=
setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
setlocal noinfercase
setlocal iskeyword=@,48-57,_,192-255
setlocal keywordprg=
setlocal nolinebreak
setlocal nolisp
setlocal nolist
setlocal makeprg=
setlocal matchpairs=(:),{:},[:]
setlocal nomodeline
setlocal modifiable
setlocal nrformats=octal,hex
set number
setlocal number
setlocal numberwidth=4
setlocal omnifunc=
setlocal path=
setlocal nopreserveindent
setlocal nopreviewwindow
setlocal quoteescape=\\
setlocal noreadonly
setlocal norightleft
setlocal rightleftcmd=search
setlocal noscrollbind
setlocal shiftwidth=4
setlocal noshortname
setlocal nosmartindent
setlocal softtabstop=0
setlocal nospell
setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
setlocal spellfile=
setlocal spelllang=en
setlocal statusline=
setlocal suffixesadd=
setlocal swapfile
setlocal synmaxcol=3000
if &syntax != 'groovy'
setlocal syntax=groovy
endif
setlocal tabstop=4
setlocal tags=
setlocal textwidth=0
setlocal thesaurus=
setlocal nowinfixheight
setlocal nowinfixwidth
setlocal wrap
setlocal wrapmargin=0
silent! normal! zE
let s:l = 37 - ((36 * winheight(0) + 28) / 57)
if s:l < 1 | let s:l = 1 | endif
exe s:l
normal! zt
37
normal! 065l
wincmd w
argglobal
edit test/com/jdbernard/pit/StatusTest.groovy
setlocal keymap=
setlocal noarabic
setlocal autoindent
setlocal balloonexpr=
setlocal nobinary
setlocal bufhidden=
setlocal buflisted
setlocal buftype=
setlocal nocindent
setlocal cinkeys=0{,0},0),:,0#,!^F,o,O,e
setlocal cinoptions=
setlocal cinwords=if,else,while,do,for,switch
setlocal comments=s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-
setlocal commentstring=/*%s*/
setlocal complete=.,w,b,u,t,i
setlocal completefunc=
setlocal nocopyindent
setlocal nocursorcolumn
setlocal nocursorline
setlocal define=
setlocal dictionary=
setlocal nodiff
setlocal equalprg=
setlocal errorformat=
setlocal expandtab
if &filetype != 'groovy'
setlocal filetype=groovy
endif
setlocal foldcolumn=0
setlocal foldenable
setlocal foldexpr=0
setlocal foldignore=#
setlocal foldlevel=0
setlocal foldmarker={{{,}}}
setlocal foldmethod=manual
setlocal foldminlines=1
setlocal foldnestmax=20
setlocal foldtext=foldtext()
setlocal formatexpr=
setlocal formatoptions=tcq
setlocal formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s*
setlocal grepprg=
setlocal iminsert=2
setlocal imsearch=2
setlocal include=
setlocal includeexpr=
setlocal indentexpr=
setlocal indentkeys=0{,0},:,0#,!^F,o,O,e
setlocal noinfercase
setlocal iskeyword=@,48-57,_,192-255
setlocal keywordprg=
setlocal nolinebreak
setlocal nolisp
setlocal nolist
setlocal makeprg=
setlocal matchpairs=(:),{:},[:]
setlocal nomodeline
setlocal modifiable
setlocal nrformats=octal,hex
set number
setlocal number
setlocal numberwidth=4
setlocal omnifunc=
setlocal path=
setlocal nopreserveindent
setlocal nopreviewwindow
setlocal quoteescape=\\
setlocal noreadonly
setlocal norightleft
setlocal rightleftcmd=search
setlocal noscrollbind
setlocal shiftwidth=4
setlocal noshortname
setlocal nosmartindent
setlocal softtabstop=0
setlocal nospell
setlocal spellcapcheck=[.?!]\\_[\\])'\"\ \ ]\\+
setlocal spellfile=
setlocal spelllang=en
setlocal statusline=
setlocal suffixesadd=
setlocal swapfile
setlocal synmaxcol=3000
if &syntax != 'groovy'
setlocal syntax=groovy
endif
setlocal tabstop=4
setlocal tags=
setlocal textwidth=0
setlocal thesaurus=
setlocal nowinfixheight
setlocal nowinfixwidth
setlocal wrap
setlocal wrapmargin=0
silent! normal! zE
let s:l = 1 - ((0 * winheight(0) + 28) / 57)
if s:l < 1 | let s:l = 1 | endif
exe s:l
normal! zt
@ -597,7 +820,7 @@ setlocal nowinfixwidth
setlocal wrap
setlocal wrapmargin=0
silent! normal! zE
let s:l = 1 - ((0 * winheight(0) + 39) / 78)
let s:l = 1 - ((0 * winheight(0) + 28) / 57)
if s:l < 1 | let s:l = 1 | endif
exe s:l
normal! zt
@ -701,7 +924,7 @@ setlocal nowinfixwidth
setlocal wrap
setlocal wrapmargin=0
silent! normal! zE
let s:l = 1 - ((0 * winheight(0) + 39) / 78)
let s:l = 1 - ((0 * winheight(0) + 28) / 57)
if s:l < 1 | let s:l = 1 | endif
exe s:l
normal! zt
@ -818,7 +1041,7 @@ setlocal nowinfixwidth
setlocal wrap
setlocal wrapmargin=0
silent! normal! zE
let s:l = 1 - ((0 * winheight(0) + 39) / 78)
let s:l = 1 - ((0 * winheight(0) + 28) / 57)
if s:l < 1 | let s:l = 1 | endif
exe s:l
normal! zt
@ -922,7 +1145,7 @@ setlocal nowinfixwidth
setlocal wrap
setlocal wrapmargin=0
silent! normal! zE
let s:l = 1 - ((0 * winheight(0) + 39) / 78)
let s:l = 1 - ((0 * winheight(0) + 28) / 57)
if s:l < 1 | let s:l = 1 | endif
exe s:l
normal! zt
@ -1039,12 +1262,12 @@ setlocal nowinfixwidth
setlocal wrap
setlocal wrapmargin=0
silent! normal! zE
let s:l = 9 - ((8 * winheight(0) + 39) / 78)
let s:l = 9 - ((6 * winheight(0) + 28) / 57)
if s:l < 1 | let s:l = 1 | endif
exe s:l
normal! zt
9
normal! 010l
normal! 0
wincmd w
argglobal
edit test/com/jdbernard/pit/FileIssueTest.groovy
@ -1143,7 +1366,7 @@ setlocal nowinfixwidth
setlocal wrap
setlocal wrapmargin=0
silent! normal! zE
let s:l = 1 - ((0 * winheight(0) + 39) / 78)
let s:l = 1 - ((0 * winheight(0) + 28) / 57)
if s:l < 1 | let s:l = 1 | endif
exe s:l
normal! zt
@ -1260,7 +1483,7 @@ setlocal nowinfixwidth
setlocal wrap
setlocal wrapmargin=0
silent! normal! zE
let s:l = 1 - ((0 * winheight(0) + 39) / 78)
let s:l = 1 - ((0 * winheight(0) + 28) / 57)
if s:l < 1 | let s:l = 1 | endif
exe s:l
normal! zt
@ -1364,7 +1587,7 @@ setlocal nowinfixwidth
setlocal wrap
setlocal wrapmargin=0
silent! normal! zE
let s:l = 1 - ((0 * winheight(0) + 39) / 78)
let s:l = 1 - ((0 * winheight(0) + 28) / 57)
if s:l < 1 | let s:l = 1 | endif
exe s:l
normal! zt
@ -1373,7 +1596,7 @@ normal! 0
wincmd w
exe 'vert 1resize ' . ((&columns * 91 + 91) / 182)
exe 'vert 2resize ' . ((&columns * 90 + 91) / 182)
tabnext 1
tabnext 3
if exists('s:wipebuf')
silent exe 'bwipe ' . s:wipebuf
endif

View File

@ -3,8 +3,7 @@ package com.jdbernard.pit
public enum Category {
BUG,
FEATURE,
TASK,
CLOSED
TASK
public static Category toCategory(String s) {
for(c in Category.values())

View File

@ -5,19 +5,21 @@ import java.lang.IllegalArgumentException as IAE
public class FileIssue extends Issue {
protected File source
public static final String fileExp = /(\d+)([bft])([ajnsv])(\d).*/
public FileIssue(File file) {
super('REPLACE_ME')
def matcher = file.name =~ /(\d{4})([bftc])(\d).*/
def matcher = file.name =~ fileExp
if (!matcher)
throw new IllegalArgumentException("${file} " +
"is not a valid Issue file.")
super.@id = matcher[0][1]
super.@category = Category.toCategory(matcher[0][2])
super.@priority = matcher[0][3].toInteger()
super.@status = Status.toStatus(matcher[0][3])
super.@priority = matcher[0][4].toInteger()
this.source = file
@ -29,12 +31,19 @@ public class FileIssue extends Issue {
source.renameTo(new File(source.canonicalFile.parentFile, getFilename()))
}
public void setStatus(Status s) {
super.setStatus(s)
source.renameTo(new File(source.canonicalFile.parentFile, getFilename()))
}
public void setPriority(int p) {
super.setPriority(p)
source.renameTo(new File(source.canonicalFile.parentFile, getFilename()))
}
public String getFilename() { return makeFilename(id, category, priority) }
public String getFilename() {
return makeFilename(id, category, status, priority)
}
public void setText(String text) {
super.setText(text)
@ -44,11 +53,11 @@ public class FileIssue extends Issue {
public boolean delete() { return source.delete() }
public static boolean isValidFilename(String name) {
return name ==~ /(\d+)([bcft])(\d).*/
return name ==~ fileExp
}
public static String makeFilename(String id, Category category,
int priority) {
Status status, int priority) {
// bounds check priority
priority = Math.min(9, Math.max(0, priority))
@ -56,10 +65,12 @@ public class FileIssue extends Issue {
//check for valid values of cateogry and id
if (category == null)
throw new IAE("Category must be non-null.")
if (status == null)
throw new IAE("Status must be non-null.")
if (!(id ==~ /\d+/))
throw new IAE( "'${id}' is not a legal value for id.")
return id + category.symbol + priority + ".rst";
return id + category.symbol + status.symbol + priority + ".rst";
}
}

View File

@ -17,7 +17,7 @@ class FileProject extends Project {
// add sub projects
if (child.isDirectory()) {
if ( child.name ==~ /\d{4}/) return // just an issue folder
if ( child.name ==~ /\d+/) return // just an issue folder
// otherwise build and add to list
projects[(child.name)] = new FileProject(child)
@ -41,6 +41,7 @@ class FileProject extends Project {
public FileIssue createNewIssue(Map options) {
if (!options) options = [:]
if (!options.category) options.category = Category.TASK
if (!options.status) options.status = Status.NEW
if (!options.priority) options.priority = 5
if (!options.text) options.text = "Default issue title.\n" +
"====================\n"
@ -52,7 +53,7 @@ class FileProject extends Project {
}
def issueFile = new File(source, FileIssue.makeFilename(id,
options.category, options.priority))
options.category, options.status, options.priority))
issueFile.createNewFile()
issueFile.write(options.text)

View File

@ -3,6 +3,7 @@ package com.jdbernard.pit
class Filter {
List<Category> categories = null
List<Status> status = null
List<String> projects = null
List<String> ids = null
int priority = 9
@ -16,6 +17,7 @@ class Filter {
public boolean accept(Issue i) {
return (i.priority <= priority &&
(!categories || categories.contains(i.category)) &&
(!status || status.contains(i.status)) &&
(!ids || ids.contains(i.id)))
}

View File

@ -6,12 +6,15 @@ public abstract class Issue {
protected String id
protected Category category
protected Status status
protected int priority
protected String text
Issue(String id, Category c = Category.TASK, int p = 9) {
Issue(String id, Category c = Category.TASK, Status s = Status.NEW,
int p = 9) {
this.id = id
this.category = c
this.status = s
this.priority = p
}
@ -26,6 +29,15 @@ public abstract class Issue {
this.category = c
}
public Status getStatus() { return status }
public void setStatus(Status s) {
if (s == null)
throw new IAE("Status cannot be null.")
this.status = s
}
public int getPriority() { return priority }
public void setPriority(int p) { priority = Math.min(9, Math.max(0, p)) }
@ -37,7 +49,7 @@ public abstract class Issue {
public void setText(String t) { text = t }
@Override
public String toString() { return "${id}(${priority}): ${category} ${title}" }
public String toString() { return "${id}(${priority}-${status}): ${category} ${title}" }
public abstract boolean delete()
}

View File

@ -0,0 +1,41 @@
package com.jdbernard.pit
public enum Status {
REASSIGNED('a'),
REJECTED('j'),
NEW('n'),
RESOLVED('s'),
VALIDATION_REQUIRED('v')
String symbol
protected Status(String s) { symbol = s }
public static Status toStatus(String str) {
Status retVal = null
for(status in Status.values()) {
if (status.symbol.equalsIgnoreCase(str) ||
status.name().startsWith(str.toUpperCase())) {
if (retVal != null)
throw new IllegalArgumentException("Request string is" +
" ambigous, '${str}' could represent ${retVal} or " +
"${status}, possibly others.")
retVal = status
}
}
if (retVal == null)
throw new IllegalArgumentException("No status matches '${str}'")
return retVal
}
public String toString() {
def words = name().split("_")
String result = ""
words.each { result += "${it[0]}${it[1..-1].toLowerCase()} " }
return result[0..-2]
}
}

View File

@ -0,0 +1,36 @@
package com.jdbernard.pit.util
import com.jdbernard.pit.*
if (args.size() != 1) {
println "Usage: Convert1_2 [dir]"
System.exit(1)
}
File rootDir = new File(args[0])
Scanner scan = new Scanner(System.in)
rootDir.eachFileRecurse { file ->
def m = file.name =~ /(\d+)([bcft])(\d).*/
if (m && file.isFile()) {
println m[0][0]
def parentFile = file.canonicalFile.parentFile
def c
def s
switch(m[0][2]) {
case "c":
println file.readLines()[0]
print "Issue was closed, was category does it belong in?"
c = Category.toCategory(scan.nextLine())
s = Status.RESOLVED
break
default:
c = Category.toCategory(m[0][2])
s = Status.NEW
break
}
println "${m[0][2]}: ${c}"
file.renameTo(new File(parentFile,
FileIssue.makeFilename(m[0][1], c, s, m[0][3].toInteger())))
}
}

View File

@ -12,24 +12,20 @@ class CategoryTest {
assertEquals toCategory("BUG"), Category.BUG
assertEquals toCategory("FEATURE"), Category.FEATURE
assertEquals toCategory("TASK"), Category.TASK
assertEquals toCategory("CLOSED"), Category.CLOSED
assertEquals toCategory("bug"), Category.BUG
assertEquals toCategory("feature"), Category.FEATURE
assertEquals toCategory("task"), Category.TASK
assertEquals toCategory("closed"), Category.CLOSED
assertEquals toCategory("b"), Category.BUG
assertEquals toCategory("f"), Category.FEATURE
assertEquals toCategory("t"), Category.TASK
assertEquals toCategory("c"), Category.CLOSED
}
@Test void testGetSymbol() {
assertEquals Category.BUG.symbol, "b"
assertEquals Category.CLOSED.symbol, "c"
assertEquals Category.FEATURE.symbol, "f"
assertEquals Category.TASK.symbol, "t"
}

View File

@ -17,14 +17,14 @@ class FileIssueTest {
testDir = new File('testdir')
testDir.mkdirs()
issueFile = new File(testDir, '0001f1.rst')
issueFile = new File(testDir, '0001fn1.rst')
issueFile.write(
"Add the killer feature to the killer app.\n" +
"=========================================\n\n" +
"Make our killer app shine!.")
issues << new FileIssue(issueFile)
issueFile = new File(testDir, '0002t5.rst')
issueFile = new File(testDir, '0002ts5.rst')
issueFile.write(
"Obtain donuts.\n" +
"==============\n\n" +
@ -42,16 +42,30 @@ class FileIssueTest {
assertEquals issues[0].category, Category.FEATURE
assertEquals issues[1].category, Category.TASK
issues[0].category = Category.CLOSED
issues[0].category = Category.TASK
issues[1].category = Category.BUG
assertEquals issues[0].category, Category.CLOSED
assertEquals issues[0].category, Category.TASK
assertEquals issues[1].category, Category.BUG
assertTrue new File(testDir, '0001c1.rst').exists()
assertTrue new File(testDir, '0002b5.rst').exists()
assertFalse new File(testDir, '0001f1.rst').exists()
assertFalse new File(testDir, '0002t5.rst').exists()
assertTrue new File(testDir, '0001tn1.rst').exists()
assertTrue new File(testDir, '0002bs5.rst').exists()
assertFalse new File(testDir, '0001fn1.rst').exists()
assertFalse new File(testDir, '0002ts5.rst').exists()
}
@Test void testSetStatus() {
assertEquals issues[0].status, Status.NEW
assertEquals issues[1].status, Status.RESOLVED
issues[0].status = Status.RESOLVED
issues[1].status = Status.REJECTED
assertTrue new File(testDir, '0001fs1.rst').exists()
assertTrue new File(testDir, '0002tj5.rst').exists()
assertFalse new File(testDir, '0001fn1.rst').exists()
assertFalse new File(testDir, '0002ts5.rst').exists()
}
@Test void testSetPriority() {
@ -65,18 +79,19 @@ class FileIssueTest {
assertEquals issues[0].priority, 2
assertEquals issues[1].priority, 9
assertTrue new File(testDir, '0001f2.rst').exists()
assertTrue new File(testDir, '0002t9.rst').exists()
assertFalse new File(testDir, '0001f1.rst').exists()
assertFalse new File(testDir, '0002t5.rst').exists()
assertTrue new File(testDir, '0001fn2.rst').exists()
assertTrue new File(testDir, '0002ts9.rst').exists()
assertFalse new File(testDir, '0001fn1.rst').exists()
assertFalse new File(testDir, '0002ts5.rst').exists()
}
@Test void testConstruction() {
File issueFile = new File(testDir, '0001f1.rst')
File issueFile = new File(testDir, '0001fn1.rst')
Issue issue = new FileIssue(issueFile)
assertEquals issue.id , "0001"
assertEquals issue.category , Category.FEATURE
assertEquals issue.status , Status.NEW
assertEquals issue.priority , 1
assertEquals issue.title , "Add the killer feature to the killer app."
assertEquals issue.text , "Add the killer feature to the killer app.\n" +
@ -86,21 +101,32 @@ class FileIssueTest {
}
@Test void testMakeFilename() {
assertEquals FileIssue.makeFilename('0001', Category.BUG, 5) , '0001b5.rst'
assertEquals FileIssue.makeFilename('0010', Category.FEATURE, 1), '0010f1.rst'
assertEquals FileIssue.makeFilename('0002', Category.CLOSED, 3) , '0002c3.rst'
assertEquals FileIssue.makeFilename('0001', Category.BUG, -2) , '0001b0.rst'
assertEquals FileIssue.makeFilename('0001', Category.TASK, 10) , '0001t9.rst'
assertEquals FileIssue.makeFilename('00101', Category.BUG, 5) , '00101b5.rst'
assertEquals FileIssue.makeFilename('0001', Category.BUG,
Status.NEW, 5), '0001bn5.rst'
assertEquals FileIssue.makeFilename('0010', Category.FEATURE,
Status.REASSIGNED, 1), '0010fa1.rst'
assertEquals FileIssue.makeFilename('0002', Category.FEATURE,
Status.REJECTED, 3), '0002fj3.rst'
assertEquals FileIssue.makeFilename('0001', Category.BUG,
Status.RESOLVED, -2), '0001bs0.rst'
assertEquals FileIssue.makeFilename('0001', Category.TASK,
Status.VALIDATION_REQUIRED, 10) , '0001tv9.rst'
assertEquals FileIssue.makeFilename('00101', Category.BUG,
Status.NEW, 5), '00101bn5.rst'
try {
FileIssue.makeFilename('badid', Category.BUG, 5)
FileIssue.makeFilename('badid', Category.BUG, Status.NEW, 5)
assertTrue 'Issue.makeFilename() succeeded with bad id input.', false
} catch (IllegalArgumentException iae) {}
try {
FileIssue.makeFilename('0002', null, 5)
FileIssue.makeFilename('0002', null, Status.NEW, 5)
assertTrue 'Issue.makeFilename() succeeded given no Category.', false
} catch (IllegalArgumentException iae) {}
try {
FileIssue.makeFilename('0002', Category.BUG, null, 5)
assertTrue 'Issue.makeFilename() succeeded given no Status.', false
} catch (IllegalArgumentException iae) {}
}
}

View File

@ -33,19 +33,19 @@ class FileProjectTest {
*/
def issueFile = new File(testDir, '0001t5.rst')
def issueFile = new File(testDir, '0001tn5.rst')
issueFile.createNewFile()
issueFile.write('Test Issue 1\n' +
'============\n\n' +
'This is the first test issue.')
issueFile = new File(testDir, '0002b5.rst')
issueFile = new File(testDir, '0002ba5.rst')
issueFile.createNewFile()
issueFile.write('Test Bug\n' +
'========\n\n' +
'Yeah, it is a test bug.')
issueFile = new File(testDir, '0003c2.rst')
issueFile = new File(testDir, '0003fs2.rst')
issueFile.createNewFile()
issueFile.write('Important Feature Request\n' +
'=========================\n\n' +
@ -54,13 +54,13 @@ class FileProjectTest {
def subDir = new File(testDir, 'subproj1')
subDir.mkdirs()
issueFile = new File(subDir, '0001f3.rst')
issueFile = new File(subDir, '0001fv3.rst')
issueFile.createNewFile()
issueFile.write('First feature in subproject\n' +
'===========================\n\n' +
'Please make the grubblers grobble.')
issueFile = new File(subDir, '0002b4.rst')
issueFile = new File(subDir, '0002bj4.rst')
issueFile.createNewFile()
issueFile.write('Zippners are not zippning.\n' +
'==========================\n\n' +
@ -129,11 +129,14 @@ class FileProjectTest {
// test correct increment of id, application of values
def newIssue = rootProj.createNewIssue(category: Category.BUG,
priority: 4, text: 'A newly made bug report.\n'+
'========================\n\n' +
'Testing the Project.createNewIssue() method.')
status: Status.REASSIGNED, priority: 4,
text: 'A newly made bug report.\n'+
'========================\n\n' +
'Testing the Project.createNewIssue() method.')
assertEquals newIssue.id, '0004'
assertEquals newIssue.category, Category.BUG
assertEquals newIssue.status, Status.REASSIGNED
assertEquals newIssue.priority, 4
assertEquals newIssue.text, 'A newly made bug report.\n'+
'========================\n\n' +
@ -145,6 +148,8 @@ class FileProjectTest {
assertEquals newIssue.id, '0000'
assertEquals newIssue.priority, 5
assertEquals newIssue.category, Category.TASK
assertEquals newIssue.status, Status.NEW
assertEquals newIssue.text, 'Default issue title.\n' +
'====================\n'

View File

@ -15,16 +15,16 @@ class FilterTest {
proj = new MockProject('proj1')
def issue = new MockIssue( '0000', Category.TASK, 5)
def issue = new MockIssue( '0000', Category.TASK, Status.NEW, 5)
proj.issues['0000'] = issue
issue = new MockIssue('0001', Category.BUG, 3)
issue = new MockIssue('0001', Category.BUG, Status.REJECTED, 3)
proj.issues['0001'] = issue
issue = new MockIssue('0002', Category.CLOSED, 9)
issue = new MockIssue('0002', Category.BUG, Status.RESOLVED, 9)
proj.issues['0002'] = issue
issue = new MockIssue('0003', Category.FEATURE, 0)
issue = new MockIssue('0003', Category.FEATURE, Status.REASSIGNED, 0)
proj.issues['0003'] = issue
def subProj = new MockProject('subproj1')
@ -69,25 +69,47 @@ class FilterTest {
@Test void testCategoryFilter() {
Filter f = new Filter(categories:
[Category.BUG, Category.FEATURE, Category.TASK])
[Category.BUG, Category.FEATURE])
assertFalse f.accept(proj.issues['0000'])
assertTrue f.accept(proj.issues['0001'])
assertTrue f.accept(proj.issues['0002'])
assertTrue f.accept(proj.issues['0003'])
f.categories = [ Category.TASK ]
assertTrue f.accept(proj.issues['0000'])
assertFalse f.accept(proj.issues['0001'])
assertFalse f.accept(proj.issues['0002'])
assertFalse f.accept(proj.issues['0003'])
f.categories = [ Category.BUG, Category.TASK ]
assertTrue f.accept(proj.issues['0000'])
assertTrue f.accept(proj.issues['0001'])
assertTrue f.accept(proj.issues['0002'])
assertFalse f.accept(proj.issues['0003'])
}
@Test void testStatusFilter() {
Filter f = new Filter(status:
[Status.NEW, Status.REASSIGNED, Status.REJECTED])
assertTrue f.accept(proj.issues['0000'])
assertTrue f.accept(proj.issues['0001'])
assertFalse f.accept(proj.issues['0002'])
assertTrue f.accept(proj.issues['0003'])
f.categories = [ Category.CLOSED ]
f.status = [ Status.RESOLVED ]
assertFalse f.accept(proj.issues['0000'])
assertFalse f.accept(proj.issues['0001'])
assertTrue f.accept(proj.issues['0002'])
assertFalse f.accept(proj.issues['0003'])
f.categories = [ Category.BUG, Category.FEATURE ]
assertFalse f.accept(proj.issues['0000'])
assertTrue f.accept(proj.issues['0001'])
assertFalse f.accept(proj.issues['0002'])
assertTrue f.accept(proj.issues['0003'])
f.status = [ Status.NEW, Status.RESOLVED ]
assertTrue f.accept(proj.issues['0000'])
assertFalse f.accept(proj.issues['0001'])
assertTrue f.accept(proj.issues['0002'])
assertFalse f.accept(proj.issues['0003'])
}
@Test void testProjectFilter() {

View File

@ -1,6 +1,8 @@
package com.jdbernard.pit
public class MockIssue extends Issue {
public MockIssue(String id, Category c, int p) { super (id, c, p) }
public MockIssue(String id, Category c, Status s, int p) {
super (id, c, s, p)
}
public boolean delete() { return true }
}

View File

@ -0,0 +1,54 @@
package com.jdbernard.pit
import org.junit.Test
import static org.junit.Assert.assertEquals
import static com.jdbernard.pit.Status.toStatus
public class StatusTest {
@Test void testToStatus() {
assertEquals Status.REASSIGNED, toStatus('REASSIGNED')
assertEquals Status.REJECTED, toStatus('REJECTED')
assertEquals Status.NEW, toStatus('NEW')
assertEquals Status.RESOLVED , toStatus('RESOLVED')
assertEquals Status.VALIDATION_REQUIRED,
toStatus('VALIDATION_REQUIRED')
assertEquals Status.REASSIGNED, toStatus('REA')
assertEquals Status.REJECTED, toStatus('REJ')
assertEquals Status.NEW, toStatus('NEW')
assertEquals Status.RESOLVED , toStatus('RES')
assertEquals Status.VALIDATION_REQUIRED,
toStatus('VAL')
assertEquals Status.REASSIGNED, toStatus('reassigned')
assertEquals Status.REJECTED, toStatus('rejected')
assertEquals Status.NEW, toStatus('new')
assertEquals Status.RESOLVED , toStatus('resolved')
assertEquals Status.VALIDATION_REQUIRED,
toStatus('validation_required')
assertEquals Status.REASSIGNED, toStatus('rea')
assertEquals Status.REJECTED, toStatus('rej')
assertEquals Status.NEW, toStatus('new')
assertEquals Status.RESOLVED , toStatus('res')
assertEquals Status.VALIDATION_REQUIRED,
toStatus('val')
assertEquals Status.REASSIGNED, toStatus('A')
assertEquals Status.REJECTED, toStatus('J')
assertEquals Status.NEW, toStatus('N')
assertEquals Status.RESOLVED , toStatus('S')
assertEquals Status.VALIDATION_REQUIRED, toStatus('V')
assertEquals Status.REASSIGNED, toStatus('a')
assertEquals Status.REJECTED, toStatus('j')
assertEquals Status.NEW, toStatus('n')
assertEquals Status.RESOLVED , toStatus('s')
assertEquals Status.VALIDATION_REQUIRED, toStatus('v')
}
}

Binary file not shown.

BIN
pit-cli/lib/pit-2.0.1.jar Normal file

Binary file not shown.

View File

@ -1,9 +1,9 @@
#Thu Feb 25 17:41:36 CST 2010
#Fri Feb 26 11:46:27 CST 2010
build.dir=build
src.dir=src
build.jar=pit-cli-${application.version}.${build.number}.jar
build.number=1
expected.application.version=1.1.8
build.number=2
expected.application.version=2.0.1
lib.dir=lib
release.dir=release
release.jar=pit-cli-${application.version}.jar

View File

@ -11,8 +11,11 @@ cli.l(longOpt: 'list', 'List issues. Unless otherwise specified it lists all '
cli.i(argName: 'id', longOpt: 'id', args: 1,
'Filter issues by id. Accepts a comma-delimited list.')
cli.c(argName: 'category', longOpt: 'category', args: 1,
'Filter issues by category (bug, feature, task, closed). Accepts a '
'Filter issues by category (bug, feature, task). Accepts a '
+ 'comma-delimited list.')
cli.t(argName: 'status', longOpt: 'status', args: 1,
'Filter issues by status (new, reassigned, rejected, resolved, ' +
'validation_required)')
cli.p(argName: 'priority', longOpt: 'priority', args: 1,
'Filter issues by priority. This acts as a threshhold, listing all issues '
+ 'greater than or equal to the given priority.')
@ -26,6 +29,8 @@ cli.P(argName: 'new-priority', longOpt: 'set-priority', args: 1,
required: false, 'Modify the priority of the selected issues.')
cli.C(argName: 'new-category', longOpt: 'set-category', args: 1,
required: false, 'Modify the category of the selected issues.')
cli.T(argName: 'new-status', longOpt: 'set-status', args: 1,
required: false, 'Modify the status of the selected issues.')
cli.n(longOpt: 'new-issue', 'Create a new issue.')
def opts = cli.parse(args)
@ -39,6 +44,10 @@ def categories = ['bug','feature','task']
if (opts.c) categories = opts.c.split(/[,\s]/)
categories = categories.collect { Category.toCategory(it) }
def statusList = ['new', 'validation_required']
if (opts.t) statusList = opts.t.split(/[,\s]/)
statusList = statusList.collect { Status.toStatus(it) }
def EOL = System.getProperty('line.separator')
// build issue list
@ -46,6 +55,7 @@ issuedb = new FileProject(new File('.'))
// build filter from options
def filter = new Filter('categories': categories,
'status': statusList,
'priority': (opts.p ? opts.p.toInteger() : 9),
'projects': (opts.r ? opts.r.toLowerCase().split(/[,\s]/).asType(List.class) : []),
'ids': (opts.i ? opts.i.split(/[,\s]/).asType(List.class) : []),
@ -88,7 +98,15 @@ else if (opts.C) {
try { cat = Category.toCategory(opts.C) }
catch (e) { println "Invalid category: ${opts.C}"; return 1 }
walkProject(issuedb, filterb) { it.category = cat }
walkProject(issuedb, filter) { it.category = cat }
}
// change status fourth
else if (opts.T) {
def status
try { status = Status.toStatus(opts.T) }
catch (e) { println "Invalid status: ${opts.T}"; return 1 }
walkProject(issuedb, filter) { it.status = status }
}
// new entry last
else if (opts.n) {

BIN
pit-swing/CHANGE ME Normal file

Binary file not shown.

View File

@ -1,4 +1,4 @@
#Sat Feb 13 08:41:16 CST 2010
app.version=1.1.8
app.version=2.0.1
app.griffon.version=0.2.1
app.name=pit-swing

View File

@ -8,7 +8,6 @@ class PITController {
def view
void mvcGroupInit(Map args) {
model.rootProject = new FileProject(new File('.'))
}
/*

Binary file not shown.

Before

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

View File

@ -1,12 +1,14 @@
package com.jdbernard.pit.swing
import com.jdbernard.pit.Category
import com.jdbernard.pit.Status
import com.jdbernard.pit.Filter
import com.jdbernard.pit.Issue
import com.jdbernard.pit.Project
import com.jdbernard.pit.FileProject
import groovy.beans.Bindable
import java.awt.GridBagConstraints as GBC
import java.awt.Font
import java.awt.Point
import java.awt.event.MouseEvent
import javax.swing.DefaultComboBoxModel
@ -33,8 +35,11 @@ projectListModels = [:]
// map of category -> list icon
categoryIcons = [:]
statusIcons = [:]
// filter for projects and issues
filter = new Filter(categories: [])
filter = new Filter(categories: [],
status: [Status.NEW, Status.VALIDATION_REQUIRED])
popupProject = null
selectedProject = model.rootProject
@ -47,6 +52,10 @@ Category.values().each {
filter.categories.add(it)
}
Status.values().each {
statusIcons[(it)] = imageIcon("/${it.name().toLowerCase()}.png")
}
/* ***************
* event methods
* ***************/
@ -111,23 +120,32 @@ newIssueDialog = dialog(title: 'New Task...', modal: true, pack: true,//size: [3
fill: GBC.HORIZONTAL),
model: new DefaultComboBoxModel(Category.values()))
label('Priority (0-9, 0 is highest priority):',
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: 3, insets: [5, 5, 0, 5],
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: 4, insets: [5, 5, 5, 5],
constraints: gbc(gridx: 0, gridy: 5, insets: [5, 5, 5, 5],
anchor: GBC.EAST))
button('Create Issue',
constraints: gbc(gridx: 1, gridy: 4, insets: [5, 5, 5, 5],
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
@ -160,7 +178,13 @@ projectPopupMenu = popupMenu() {
issuePopupMenu = popupMenu() {
menuItem('New Issue...',
actionPerformed: { newIssueDialog.visible = true })
actionPerformed: {
titleTextField.text = ""
categoryComboBox.selectedIndex = 0
statusComboBox.selectedIndex = 0
prioritySpinner.setValue(5)
newIssueDialog.visible = true
})
menuItem('Delete Issue',
actionPerformed: {
@ -180,6 +204,20 @@ issuePopupMenu = popupMenu() {
if (!popupIssue) return
popupIssue.category = category
issueList.invalidate()
issueList.repaint()
})
}
}
menu('Change Status') {
Status.values().each { status ->
menuItem(status.toString(),
icon: statusIcons[(status)],
actionPerformed: {
if (!popupIssue) return
popupIssue.status = status
issueList.invalidate()
issueList.repaint()
})
}
}
@ -198,12 +236,13 @@ issuePopupMenu = popupMenu() {
return
}
issueList.invalidate()
issueList.repaint()
})
}
frame = application(title:'Personal Issue Tracker',
locationRelativeTo: null,
minimumSize: [600, 400],
minimumSize: [800, 500],
//size:[320,480],
pack:true,
//location:[50,50],
@ -231,21 +270,42 @@ frame = application(title:'Personal Issue Tracker',
}
menu('View') {
Category.values().each {
checkBoxMenuItem(it.toString(),
selected: filter.categories.contains(it),
actionPerformed: { evt ->
def cat = Category.toCategory(evt.source.text)
if (filter.categories.contains(cat)) {
filter.categories.remove(cat)
evt.source.selected = false
} else {
filter.categories.add(cat)
evt.source.selected = true
}
projectListModels.clear()
displayProject(selectedProject)
})
menu('Category') {
Category.values().each {
checkBoxMenuItem(it.toString(),
selected: filter.categories.contains(it),
actionPerformed: { evt ->
def cat = Category.toCategory(evt.source.text)
if (filter.categories.contains(cat)) {
filter.categories.remove(cat)
evt.source.selected = false
} else {
filter.categories.add(cat)
evt.source.selected = true
}
projectListModels.clear()
displayProject(selectedProject)
})
}
}
menu('Status') {
Status.values().each {
checkBoxMenuItem(it.toString(),
selected: filter.status.contains(it),
actionPerformed: { evt ->
def st = Status.toStatus(evt.source.text[0..5])
if (filter.status.contains(st)) {
filter.status.remove(st)
evt.source.selected = false
} else {
filter.status.add(st)
evt.source.selected = true
}
projectListModels.clear()
displayProject(selectedProject)
})
}
}
}
@ -266,7 +326,10 @@ frame = application(title:'Personal Issue Tracker',
if (model.rootProject) {
projectTree.rootVisible = model.rootProject.issues.size()
new DefaultTreeModel(makeNodes(model.rootProject))
} else new DefaultTreeModel()
} else {
projectTree.rootVisible = false
new DefaultTreeModel(new DefaultMutableTreeNode())
}
}),
valueChanged: { evt ->
selectedProject = evt?.newLeadSelectionPath?.lastPathComponent?.userObject ?: model.rootProject
@ -280,6 +343,8 @@ frame = application(title:'Personal Issue Tracker',
evt.x, evt.y)
}
})
projectTree.model = new DefaultTreeModel(new DefaultMutableTreeNode())
projectTree.rootVisible = false
projectTree.selectionModel.selectionMode =
TreeSelectionModel.SINGLE_TREE_SELECTION
@ -292,7 +357,8 @@ frame = application(title:'Personal Issue Tracker',
scrollPane(constraints: "top") {
issueList = list(
cellRenderer: new IssueListCellRenderer(
issueIcons: categoryIcons),
categoryIcons: categoryIcons,
statusIcons: statusIcons),
selectionMode: ListSelectionModel.SINGLE_SELECTION,
valueChanged: { displayIssue(issueList.selectedValue) },
mouseClicked: { evt ->
@ -306,7 +372,19 @@ frame = application(title:'Personal Issue Tracker',
})
}
scrollPane(constraints: "bottom") {
issueTextArea = textArea()
issueTextArea = textArea(
font: new Font(Font.MONOSPACED, Font.PLAIN, 12),
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
})
}
}
}

Binary file not shown.

BIN
pit-swing/lib/pit-2.0.1.jar Normal file

Binary file not shown.

View File

@ -0,0 +1,60 @@
package com.jdbernard.pit.swing
import java.awt.Component
import java.awt.Graphics
import javax.swing.Icon
/**
* This class provides a composite icon. A composite icon
* draws its parts one on the other, all aligned to the
* left top corner. The size of the compsite icon is the
* max size of its parts.
*
*/
public class CompositeIcon implements Icon {
List<Icon> icons
/**
* Construct a composite icon.
*
* @param i The parts.
*/
public CompositeIcon(List<Icon> i) { icons = i; }
/**
* Draw the icon at the specified location. Icon implementations
* may use the Component argument to get properties useful for
* painting, e.g. the foreground or background color.
*
* @param c The component to take attributes from.
* @param g The graphics port to draw into.
* @param x The x drawing coordinate.
* @param y The y drawing coordinate.
*/
public void paintIcon(Component c, Graphics g, int x, int y) {
icons.each { it.paintIcon(c, g, x, y) }
}
/**
* Returns the icon's width.
*
* @return an int specifying the fixed width of the icon.
*/
public int getIconWidth() {
int width = 0;
icons.each { width = Math.max(width, it.iconWidth) }
return width;
}
/**
* Returns the icon's height.
*
* @return an int specifying the fixed height of the icon.
*/
public int getIconHeight() {
int height = 0;
icons.each { height = Math.max(height, it.iconHeight) }
return height;
}
}

View File

@ -1,5 +1,7 @@
package com.jdbernard.pit.swing
import com.jdbernard.pit.Category
import com.jdbernard.pit.Status
import java.awt.Component
import javax.swing.Icon
import javax.swing.JList
@ -7,14 +9,23 @@ import javax.swing.DefaultListCellRenderer
public class IssueListCellRenderer extends DefaultListCellRenderer {
Map<Category, Icon> issueIcons
Map<Category, Icon> categoryIcons
Map<Status, Icon> statusIcons
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean selected, boolean hasFocus) {
def component = super.getListCellRendererComponent(list, value, index,
selected, hasFocus)
if (issueIcons[(value.category)])
component.setIcon(issueIcons[(value.category)])
def icon
if (categoryIcons[(value.category)]) {
icon = categoryIcons[(value.category)]
if (statusIcons[(value.status)])
icon = new CompositeIcon([icon, statusIcons[(value.status)]])
}
if (icon) setIcon(icon)
component.text = "<html><tt>${value.id} (${value.priority}): </tt>" +
"${value.title}</html>"
return component

0
pit-swing/stacktrace.log Normal file
View File

Binary file not shown.

BIN
release/lib/pit-2.0.1.jar Normal file

Binary file not shown.

BIN
release/pit-swing-2.0.1.jar Normal file

Binary file not shown.

View File

@ -1 +1 @@
application.version=1.1.8
application.version=2.0.1