Commit bf37ce10 authored by Nicolas Boulenguez's avatar Nicolas Boulenguez

Imported Upstream version 1.4.1

parent cd6aeaf3
......@@ -22,6 +22,21 @@ from changetypes import *
releases = [
Release('1.4.1', 'September 14, 2014',
summary='''This is a bugfix release.''',
bugsFixed=[
Bugv2('''Increase taskbar icon size on Linux to avoid dead places.''',
'1536'),
Bugv2('''Display time spent in decimal format in task viewer if the
option is set.''', '1534'),
Bugv2('''Fix todo.txt export when dates have a year < 1900''', '1541'),
Bugv2('''Avoid locking in Dropbox folder on Windows.''', '1540'),
Bugv2('''Backup and save would fail if the path to the user's home contained
a non-ASCII character''', '1547', '1546'),
Bugv2('''Fix stop effort tooltip.''', '1537'),
],
),
Release('1.4.0', 'July 3, 2014',
summary='''This is a major feature release.''',
featuresAdded=[
......
......@@ -16,14 +16,14 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import time, string, datetime, timedelta # pylint: disable=W0402
import time, string, datetime, re, timedelta # pylint: disable=W0402
from taskcoachlib import patterns
from .fix import StrftimeFix
infinite = datetime.date.max
class RealDate(datetime.date):
class RealDate(StrftimeFix, datetime.date):
def __add__(self, delta):
newdate = super(RealDate, self).__add__(delta)
return RealDate(newdate.year, newdate.month, newdate.day)
......
......@@ -17,11 +17,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import datetime, timedelta, re, time
from taskcoachlib import operating_system
from .date import Date
from .fix import StrftimeFix
class DateTime(datetime.datetime):
class DateTime(StrftimeFix, datetime.datetime):
secondsPerMinute = 60
minutesPerHour = 60
hoursPerDay = 24
......@@ -41,8 +41,8 @@ class DateTime(datetime.datetime):
hour=dateTime.hour, minute=dateTime.minute, second=dateTime.second,
microsecond=dateTime.microsecond)
def strftime(self, *args):
return operating_system.decodeSystemString(super(DateTime, self).strftime(*args))
def date(self):
return Date(self.year, self.month, self.day)
def weeknumber(self):
return self.isocalendar()[1]
......
'''
Task Coach - Your friendly task manager
Copyright (C) 2014 Task Coach developers <developers@taskcoach.org>
Task Coach is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Task Coach is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
from taskcoachlib import operating_system
import re
class StrftimeFix(object):
"""Mixin class to fix strftime() so that it works with years < 1900"""
def strftime(self, *args):
if self.year >= 1900:
return operating_system.decodeSystemString(super(StrftimeFix, self).strftime(*args))
result = self.replace(year=self.year + 1900).strftime(*args)
return re.sub(str(self.year + 1900), str(self.year), result)
......@@ -85,7 +85,12 @@ class IconProvider(object):
def __init__(self):
self.__iconCache = dict()
self.__iconSizeOnCurrentPlatform = 128 if operating_system.isMac() else 16
if operating_system.isMac():
self.__iconSizeOnCurrentPlatform = 128
elif operating_system.isGTK():
self.__iconSizeOnCurrentPlatform = 48
else:
self.__iconSizeOnCurrentPlatform = 16
def getIcon(self, iconTitle):
''' Return the icon. Use a cache to prevent leakage of GDI object
......@@ -106,6 +111,11 @@ class IconProvider(object):
def getIconFromArtProvider(self, iconTitle, iconSize=None):
size = iconSize or self.__iconSizeOnCurrentPlatform
# I just spent two hours trying to get rid of garbage in the icon
# background on KDE. I give up.
if operating_system.isGTK():
return wx.ArtProvider_GetIcon(iconTitle, wx.ART_FRAME_ICON, (size, size))
# wx.ArtProvider_GetIcon doesn't convert alpha to mask, so we do it
# ourselves:
bitmap = wx.ArtProvider_GetBitmap(iconTitle, wx.ART_FRAME_ICON,
......
......@@ -1735,8 +1735,13 @@ class EffortStop(EffortListCommand, TaskListCommand, ViewerCommand):
bitmap2='clock_stop_icon', menuText=self.defaultMenuText,
helpText=self.defaultHelpText, kind=wx.ITEM_CHECK, *args, **kwargs)
self.__tracker = effort.EffortListTracker(self.effortList)
for subtype in ['', '.added', '.removed']:
self.__tracker.subscribe(self.__onEffortsChanged, 'effortlisttracker%s' % subtype)
self.__currentBitmap = None # Don't know yet what our bitmap is
def __onEffortsChanged(self, efforts):
self.updateUI()
def efforts(self):
selectedEfforts = set()
for item in self.viewer.curselection():
......
......@@ -101,6 +101,11 @@ class BaseTaskViewer(mixin.SearchableViewerMixin, # pylint: disable=W0223
super(BaseTaskViewer, self).detach()
self.statusMessages = None # Break cycle
def _renderTimeSpent(self, *args, **kwargs):
if self.settings.getboolean('feature', 'decimaltime'):
return render.timeSpentDecimal(*args, **kwargs)
return render.timeSpent(*args, **kwargs)
def onAppearanceSettingChange(self, value): # pylint: disable=W0613
if self:
wx.CallAfter(self.refresh) # Let domain objects update appearance first
......@@ -452,6 +457,10 @@ class SquareTaskViewer(BaseTaskTreeViewer):
self.__orderBy = 'revenue'
self.__transformTaskAttribute = lambda x: x
self.__zero = 0
self.renderer = dict(budget=render.budget, timeSpent=self._renderTimeSpent,
fixedFee=render.monetaryAmount,
revenue=render.monetaryAmount,
priority=render.priority)
super(SquareTaskViewer, self).__init__(*args, **kwargs)
sortKeys = eval(self.settings.get(self.settingsSection(), 'sortby'))
orderBy = sortKeys[0] if sortKeys else 'budget'
......@@ -585,11 +594,6 @@ class SquareTaskViewer(BaseTaskTreeViewer):
# Helper methods
renderer = dict(budget=render.budget, timeSpent=render.timeSpent,
fixedFee=render.monetaryAmount,
revenue=render.monetaryAmount,
priority=render.priority)
def render(self, value):
return self.renderer[self.__orderBy](value)
......@@ -1289,7 +1293,7 @@ class TaskViewer(mixin.AttachmentDropTargetMixin, # pylint: disable=W0223
task.completed())
def renderTimeSpent(self, task):
return self.renderedValue(task, task.timeSpent, render.timeSpent)
return self.renderedValue(task, task.timeSpent, self._renderTimeSpent)
def renderBudget(self, task):
return self.renderedValue(task, task.budget, render.budget)
......
......@@ -22,10 +22,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
# Edit these for every release:
version = '1.4.0' # Current version number of the application
version = '1.4.1' # Current version number of the application
tskversion = 37 # Current version number of the task file format, changed to 37 for release 1.3.23.
release_day = '3' # Day number of the release, 1-31, as string
release_month = 'July' # Month of the release in plain English
release_day = '14' # Day number of the release, 1-31, as string
release_month = 'September' # Month of the release in plain English
release_year = '2014' # Year of the release as string
release_status = 'stable' # One of 'alpha', 'beta', 'stable'
......
......@@ -27,6 +27,10 @@ import bz2, hashlib
from xml.etree import ElementTree as ET
def SHA(filename):
return hashlib.sha1(filename.encode('UTF-8')).hexdigest()
def compressFile(srcName, dstName):
with file(srcName, 'rb') as src:
dst = bz2.BZ2File(dstName, 'w')
......@@ -74,24 +78,23 @@ class BackupManifest(object):
return len(self.listBackups(filename)) != 0
def backupPath(self, filename):
path = os.path.join(self.__settings.pathToBackupsDir(), hashlib.sha1(filename).hexdigest())
path = os.path.join(self.__settings.pathToBackupsDir(), SHA(filename))
if not os.path.exists(path):
os.makedirs(path)
return path
def addFile(self, filename):
sha = hashlib.sha1(filename).hexdigest()
self.__files[sha] = filename
self.__files[SHA(filename)] = filename
def removeFile(self, filename):
sha = hashlib.sha1(filename).hexdigest()
sha = SHA(filename)
if sha in self.__files:
del self.__files[sha]
def restoreFile(self, filename, dateTime, dstName):
if os.path.exists(dstName):
os.remove(dstName)
sha = hashlib.sha1(filename).hexdigest()
sha = SHA(filename)
src = bz2.BZ2File(os.path.join(self.__settings.pathToBackupsDir(), sha, dateTime.strftime('%Y%m%d%H%M%S.bak')), 'r')
try:
with file(dstName, 'wb') as dst:
......@@ -203,13 +206,13 @@ class AutoBackup(object):
return deltas[0][1]
def backupFiles(self, taskFile, glob=glob.glob): # pylint: disable=W0621
sha = hashlib.sha1(taskFile.filename()).hexdigest()
sha = SHA(taskFile.filename())
root = os.path.join(self.__settings.pathToBackupsDir(), sha)
return sorted(glob('%s.bak' % os.path.join(root, '[0-9]' * 14)))
def backupFilename(self, taskFile, now=date.DateTime.now):
''' Generate a backup filename for the specified date/time. '''
sha = hashlib.sha1(taskFile.filename()).hexdigest()
sha = SHA(taskFile.filename())
return os.path.join(self.__settings.pathToBackupsDir(), sha, now().strftime('%Y%m%d%H%M%S.bak'))
@staticmethod
......
......@@ -28,6 +28,16 @@ from taskcoachlib.filesystem import FilesystemNotifier, FilesystemPollerNotifier
from taskcoachlib.thirdparty.pubsub import pub
def _isDropbox(path):
path = os.path.abspath(path)
while True:
if os.path.exists(os.path.join(path, '.dropbox.cache')):
return True
path, name = os.path.split(path)
if name == '':
return False
class TaskCoachFilesystemNotifier(FilesystemNotifier):
def __init__(self, taskFile):
self.__taskFile = taskFile
......@@ -96,13 +106,7 @@ class SafeWriteFile(object):
idx += 1
def _isDropbox(self):
path = os.path.abspath(os.path.dirname(self.__filename))
while True:
if os.path.exists(os.path.join(path, '.dropbox.cache')):
return True
path, name = os.path.split(path)
if name == '':
return False
return _isDropbox(os.path.dirname(self.__filename))
class TaskFile(patterns.Observer):
......@@ -567,6 +571,23 @@ class TaskFile(patterns.Observer):
self.markDirty()
class DummyLockFile(object):
def acquire(self, timeout=None):
pass
def release(self):
pass
def is_locked(self):
return True
def i_am_locking(self):
return True
def break_lock(self):
pass
class LockedTaskFile(TaskFile):
''' LockedTaskFile adds cooperative locking to the TaskFile. '''
......@@ -585,8 +606,15 @@ class LockedTaskFile(TaskFile):
return True
return False
def __isDropbox(self, filename):
return _isDropbox(os.path.dirname(filename))
def __createLockFile(self, filename):
return (lockfile.MkdirFileLock if self.__isFuse(filename) else lockfile.FileLock)(filename)
if operating_system.isWindows() and self.__isDropbox(filename):
return DummyLockFile()
if self.__isFuse(filename):
return lockfile.MkdirFileLock(filename)
return lockfile.FileLock(filename)
def is_locked(self):
return self.__lock and self.__lock.is_locked()
......
......@@ -18,6 +18,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import wxversion
wxversion.select(["2.8-unicode", "3.0"], optionsRequired=True)
import sys, unittest, os, time, wx, logging
projectRoot = os.path.abspath('..')
if projectRoot not in sys.path:
......
......@@ -94,8 +94,11 @@ class DateTest(test.TestCase):
def testSubstractTwoDates_BothInfinite(self):
self.assertEqual(date.TimeDelta(), date.Date() - date.Date())
def testFormat1900(self):
self.assertEqual(date.DateTime(2, 5, 19, 0, 0, 0).strftime('%Y%m%d'), '20519')
class FactoriesTest(test.TestCase):
def testParseDate(self):
parsed = date.parseDate("2004-1-1")
......
......@@ -80,3 +80,6 @@ class DateTimeTest(test.TestCase):
expected = date.DateTime(2003, 12, 31)
actual = date.LastDayOfCurrentMonth(localtime=lambda: (2003, 12, 1))
self.assertEqual(expected, actual)
def testFormat1900(self):
self.assertEqual(date.DateTime(2, 5, 19, 0, 0, 0).strftime('%Y%m%d'), '20519')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment