|
ToDoList keeps abbreviating task names and adding ellipsis to the end even if there is plenty of space to show the full task name. The only way to see the full task name in the task list is to edit the task name (yes I see full task name is in the status bar but it's too small and I'd like to see it in the task list) I've just spent 15 minutes and couldn't find the option to display full task name. Could anyone point in the right direction?
modified 27-Aug-22 17:31pm.
|
|
|
|
|
This is a bug, but unfortunately I've never been able to reproduce it.
Perhaps you could report it with supporting information on our dedicated forum[^]...
|
|
|
|
|
had clicked max on somehow
Cant update the other item as it is held for moderation
Regards
Michael Hawksworth
|
|
|
|
|
|
Hiya Dan, hope things are going well (is this 10 years I have been using this!).
I seem to have lost the edit boxes, doesn't matter if I move them around they don't seem to come back.
Michael.
Regards
Michael Hawksworth
|
|
|
|
|
Hello, I have been improving the export to MyLifeOrganized.
Now I can export all my important information but cannot export the formatted text of the comments...
I can get the plain text comments with the following code:
LPCTSTR szComments = pSrcTaskFile->GetTaskComments(hTask);
But, how could I get the formatted version of it? Or, at least, to know if the text has been formatted or not?
Ideally, I would like to convert that formatted text to a markdown text or a html text, so MyLifeOrganized can display it...
Any ideas or help would be greatly appreciated!!!
In case you also are interested to export your tasks to MyLifeOrganized, here it is my improved version of the exporter. Now I export:
- Task colors
- Flag state
- Completion state
- Comments
- Link or multiple links
- Task dependency or multiple dependencies
- First level task exported in bold, like in ToDoList
- Folders and projects: These are not available in ToDoList, but I set a criteria for setting those in MyLifeOrganized
- Fixed importance scale
- Fixed dates
#include "stdafx.h"
#include "MLOImport.h"
#include "MLOExporter.h"
#include "..\shared\xmlfileex.h"
#include "..\shared\datehelper.h"
#include "..\shared\timehelper.h"
#include "..\shared\misc.h"
#include "..\3rdParty\T64Utils.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
CMLOExporter::CMLOExporter()
{
m_icon.Load(IDI_MYLIFEORGANISED);
}
CMLOExporter::~CMLOExporter()
{
}
void CMLOExporter::SetLocalizer(ITransText* )
{
}
IIMPORTEXPORT_RESULT CMLOExporter::Export(const ITaskList* pSrcTaskFile, LPCTSTR szDestFilePath, DWORD , IPreferences* , LPCTSTR )
{
const ITASKLISTBASE* pTasks = GetITLInterface<ITASKLISTBASE>(pSrcTaskFile, IID_TASKLISTBASE);
if (pTasks == NULL)
{
ASSERT(0);
return IIER_BADINTERFACE;
}
CXmlFile fileDest(_T("MyLifeOrganized-xml"));
fileDest.SetXmlHeader(DEFAULT_UTF8_HEADER);
fileDest.AddItem(_T("ver"), _T("1.2"));
CXmlItem* pXITasks = fileDest.AddItem(_T("TaskTree"));
if (!ExportTask(pTasks, pSrcTaskFile->GetFirstTask(), pXITasks, TRUE))
return IIER_SOMEFAILED;
ExportPlaces(pTasks, fileDest.Root());
if (!fileDest.Save(szDestFilePath, SFEF_UTF8WITHOUTBOM))
return IIER_BADFILE;
return IIER_SUCCESS;
}
IIMPORTEXPORT_RESULT CMLOExporter::Export(const IMultiTaskList* pSrcTaskFile, LPCTSTR szDestFilePath, DWORD , IPreferences* , LPCTSTR )
{
CXmlFile fileDest(_T("MyLifeOrganized-xml"));
fileDest.SetXmlHeader(DEFAULT_UTF8_HEADER);
fileDest.AddItem(_T("ver"), _T("1.2"));
CXmlItem* pXITasks = fileDest.AddItem(_T("TaskTree"));
for (int nTaskList = 0; nTaskList < pSrcTaskFile->GetTaskListCount(); nTaskList++)
{
const ITASKLISTBASE* pTasks = GetITLInterface<ITASKLISTBASE>(pSrcTaskFile->GetTaskList(nTaskList), IID_TASKLISTBASE);
if (pTasks == NULL)
{
ASSERT(0);
return IIER_BADINTERFACE;
}
if (!ExportTask(pTasks, pTasks->GetFirstTask(), pXITasks, TRUE))
return IIER_SOMEFAILED;
ExportPlaces(pTasks, fileDest.Root());
}
if (!fileDest.Save(szDestFilePath, SFEF_UTF8WITHOUTBOM))
return IIER_BADFILE;
return IIER_SUCCESS;
}
bool CMLOExporter::ExportTask(const ITASKLISTBASE* pSrcTaskFile, HTASKITEM hTask,
CXmlItem* pXIDestParent, BOOL bAndSiblings)
{
if (!hTask)
return true;
CXmlItem* pXIDestItem = pXIDestParent->AddItem(_T("TaskNode"));
if (!pXIDestItem)
return false;
pXIDestItem->AddItem(_T("Caption"), pSrcTaskFile->GetTaskTitle(hTask));
int nPriority = pSrcTaskFile->GetTaskPriority(hTask, FALSE);
int nImportance = max(nPriority, 0) * 20;
pXIDestItem->AddItem(_T("Importance"), nImportance, XIT_ELEMENT);
CString strIDD;
unsigned long nIDD = pSrcTaskFile->GetTaskID(hTask);
strIDD.Format(_T("{00000000-0000-1111-0000-%012lu}"), nIDD);
pXIDestItem->AddItem(_T("IDD"), strIDD, XIT_ELEMENT);
CString strNote;
int nDependencies = pSrcTaskFile->GetTaskDependencyCount(hTask);
if (nDependencies > 0)
{
CXmlItem* pXIDestDependenciesItem = pXIDestItem->AddItem(_T("Dependency"), _T(""), XIT_ELEMENT);
if (!pXIDestDependenciesItem)
return false;
CString strUID;
CString strDependencyCount;
for (int nDependency = 0; nDependency < nDependencies; ++nDependency)
{
LPCWSTR pStrDependency = pSrcTaskFile->GetTaskDependency(hTask, nDependency);
strDependencyCount.Format(_T("%d"), nDependency + 1);
strNote += _T("ToDoList dependency (") + strDependencyCount + _T("): [ID " + CString(pStrDependency) + _T("] "));
unsigned long nID = _wtoi(pStrDependency);
HTASKITEM hTaskDependency = pSrcTaskFile->FindTask(nID);
if (hTaskDependency)
{
unsigned long nUID = pSrcTaskFile->GetTaskID(hTaskDependency);
strUID.Format(_T("{00000000-0000-1111-0000-%012lu}"), nUID);
pXIDestDependenciesItem->AddItem(_T("UID"), strUID, XIT_ELEMENT);
strNote += pSrcTaskFile->GetTaskTitle(hTaskDependency);
}
strNote += _T("\n");
}
}
time64_t tDate = T64Utils::T64_NULL;
if (pSrcTaskFile->GetTaskCreationDate64(hTask, tDate))
pXIDestItem->AddItem(_T("Created"), FormatDate(tDate), XIT_ELEMENT);
if (pSrcTaskFile->GetTaskDoneDate64(hTask, tDate))
pXIDestItem->AddItem(_T("CompletionDateTime"), FormatDate(tDate), XIT_ELEMENT);
if (pSrcTaskFile->GetTaskDueDate64(hTask, false, tDate))
pXIDestItem->AddItem(_T("DueDateTime"), FormatDate(tDate), XIT_ELEMENT);
TDC_UNITS nUnits;
double dTimeEst = pSrcTaskFile->GetTaskTimeEstimate(hTask, nUnits, FALSE);
if (dTimeEst > 0.0)
{
TH_UNITS nTHUnits = MapUnitsToTHUnits(nUnits);
pXIDestItem->AddItem(_T("EstimateMax"), CTimeHelper().Convert(dTimeEst, nTHUnits, THU_DAYS), XIT_ELEMENT);
}
CString strLink;
static const CString urlProtocol = _T("://");
static const CString fileProtocol = _T("file://");
int nLinks = pSrcTaskFile->GetTaskFileLinkCount(hTask);
if (nLinks > 1)
{
CString strLinkCount;
for (int nLinkCount = 0; nLinkCount < nLinks; ++nLinkCount)
{
strLink = pSrcTaskFile->GetTaskFileLink(hTask, nLinkCount);
strLinkCount.Format(_T("%d"), nLinkCount+1);
strNote += _T("ToDoList link (") + strLinkCount + _T("): <");
if (strLink.Find(urlProtocol) < 0)
strNote += fileProtocol;
strNote += CString(strLink) + _T(">\n");
}
}
else
{
strLink = pSrcTaskFile->GetTaskFileLinkPath(hTask);
if (!Misc::IsEmpty(strLink))
{
strNote += _T("ToDoList link: <");
if (strLink.Find(urlProtocol) < 0)
strNote += fileProtocol;
strNote += CString(strLink) + _T(">\n");
}
}
LPCTSTR szComments = pSrcTaskFile->GetTaskComments(hTask);
bool bHasFormattedComments = pSrcTaskFile->GetTaskComments(hTask);
if (bHasFormattedComments)
strNote += _T("ToDoList Comments: ***LOST THE FORMATTED TEXT!!!***\n");
if ((!Misc::IsEmpty(strNote)) && (!Misc::IsEmpty(szComments)))
strNote += _T("\n");
if (!Misc::IsEmpty(szComments))
strNote += szComments;
if (!strNote.IsEmpty())
pXIDestItem->AddItem(_T("Note"), strNote, XIT_ELEMENT);
if ((pSrcTaskFile->GetFirstTask(hTask) != nullptr)) {
if (strNote.IsEmpty())
pXIDestItem->AddItem(_T("HideInToDoThisTask"), -1, XIT_ELEMENT); else
pXIDestItem->AddItem(_T("IsProject"), -1, XIT_ELEMENT); }
bool bTaskFlagged = pSrcTaskFile->IsTaskFlagged(hTask, false);
if (bTaskFlagged)
pXIDestItem->AddItem(_T("Flag"), _T("Red Flag"), XIT_ELEMENT);
CXmlItem* pXIDestCustomFormatItem = pXIDestItem->AddItem(_T("CustomFormat"), _T(""), XIT_ELEMENT);
if (!pXIDestCustomFormatItem)
return false;
bool bIsParentTask = pSrcTaskFile->IsTaskParent(hTask);
if (bIsParentTask)
pXIDestCustomFormatItem->AddItem(_T("Bold"), _T("1"), XIT_ELEMENT);
unsigned long unTaskColor = pSrcTaskFile->GetTaskColor(hTask);
CString strColor;
strColor.Format(_T("%lu"), unTaskColor);
pXIDestCustomFormatItem->AddItem(_T("FontColor"), strColor, XIT_ELEMENT);
pXIDestCustomFormatItem->AddItem(_T("SideBarColor"), strColor, XIT_ELEMENT);
if (!ExportTask(pSrcTaskFile, pSrcTaskFile->GetFirstTask(hTask), pXIDestItem, TRUE))
return false;
if (bAndSiblings)
{
HTASKITEM hSibling = pSrcTaskFile->GetNextTask(hTask);
while (hSibling)
{
if (!ExportTask(pSrcTaskFile, hSibling, pXIDestParent, FALSE))
return false;
hSibling = pSrcTaskFile->GetNextTask(hSibling);
}
}
return true;
}
CString CMLOExporter::FormatDate(time64_t tDate)
{
COleDateTime date = CDateHelper::GetDate(tDate);
return CDateHelper::FormatDate(date, DHFD_ISO | DHFD_TIME, 'T');
}
void CMLOExporter::BuildPlacesMap(const ITASKLISTBASE* pSrcTaskFile, HTASKITEM hTask,
CMapStringToString& mapPlaces, BOOL bAndSiblings)
{
if (!hTask)
return;
int nCat = pSrcTaskFile->GetTaskCategoryCount(hTask);
while (nCat--)
{
CString sCat = pSrcTaskFile->GetTaskCategory(hTask, nCat);
CString sCatUpper(sCat);
sCat.MakeUpper();
mapPlaces[sCatUpper] = sCat;
}
BuildPlacesMap(pSrcTaskFile, pSrcTaskFile->GetFirstTask(hTask), mapPlaces, TRUE);
if (bAndSiblings)
{
HTASKITEM hSibling = pSrcTaskFile->GetNextTask(hTask);
while (hSibling)
{
BuildPlacesMap(pSrcTaskFile, hSibling, mapPlaces, FALSE);
hSibling = pSrcTaskFile->GetNextTask(hSibling);
}
}
}
void CMLOExporter::ExportPlaces(const ITASKLISTBASE* pSrcTaskFile, CXmlItem* pDestPrj)
{
CMapStringToString mapPlaces;
BuildPlacesMap(pSrcTaskFile, pSrcTaskFile->GetFirstTask(), mapPlaces, TRUE);
if (mapPlaces.GetCount())
{
CXmlItem* pXIResources = pDestPrj->AddItem(_T("PlacesList"));
CString sPlace, sPlaceUpper;
POSITION pos = mapPlaces.GetStartPosition();
while (pos)
{
mapPlaces.GetNextAssoc(pos, sPlaceUpper, sPlace);
CXmlItem* pXIRes = pXIResources->AddItem(_T("TaskPlace"));
if (pXIRes)
pXIRes->AddItem(_T("Caption"), sPlace);
}
}
}
TH_UNITS CMLOExporter::MapUnitsToTHUnits(TDC_UNITS nUnits)
{
switch (nUnits)
{
case TDCU_MINS: return THU_MINS;
case TDCU_HOURS: return THU_HOURS;
case TDCU_DAYS: return THU_DAYS;
case TDCU_WEEKDAYS: return THU_WEEKDAYS;
case TDCU_WEEKS: return THU_WEEKS;
case TDCU_MONTHS: return THU_MONTHS;
case TDCU_YEARS: return THU_YEARS;
}
ASSERT(0);
return THU_NULL;
}
Hope it is useful for someone!
|
|
|
|
|
Many thanks Hugo, I will definitely integrate your changes into the GitHub repo.
Quote: How can I get the formatted text of the comments of a task?
This is tricky because the rich text comments is also a plugin of its own, so the app knows nothing about the format.
However, for your personal build, the following should work:
1. Get the non-text task comments using:
CString sComments = pTasks->GetTaskAttribute(hTask, L"CUSTOMCOMMENTS");
CString sType = pTasks->GetTaskAttribute(hTask, L"COMMENTSTYPE");
2. Check 'sType' against 'L"849CF988-79FE-418A-A40D-01FE3AFCAB2C"' which is the Rich Text plugin ID (RTFContentCtrl\RTFContentCtrl.h)
3. Base64 decode the text into a binary blob, using the method in 'CTaskFile::GetTaskCustomComments()'.
4. Decompress and extract the RTF like so (ConvertRTFToHTML\ConvertRTFToHTMLDlg.cpp):
CRtfHtmlConverter rtfHtml;
CBinaryData content(blob from 3.);
const unsigned char* pContent = content.Get();
int nLength = content.GetLength();
unsigned char* pDecompressed = NULL;
if (nLength && !rtfHtml.IsRTF((const char*)pTDI->customComments.Get()))
{
int nLenDecompressed = 0;
if (Compression::Decompress(pContent, nLength, pDecompressed, nLenDecompressed))
{
pContent = pDecompressed;
nLength = nLenDecompressed;
if (!rtfHtml.IsRTF((const char*)pContent))
{
nLength = 0;
}
}
else
{
nLength = 0;
}
}
The only other thing to be aware of is that RTF is always in ANSI format even if the surrounding code is UNICODE.
Also, if you want it to be a two-way process then you would also need to modify the MLOImporter similarly.
modified 10-Aug-22 2:44am.
|
|
|
|
|
Thank you very much, Dan!
I really appreciate your help, it was key to fix my issue. I finally was able to export the formatted text in HTML, I finally used the HTML approach from your other comment. The methods on this thread seems not to be exported in the interface, and it was much easier with the other approach. I share with you the code on the other answer!
About the two-way process, and importing back to ToDoList, it will be very interesting to implement... but I will wait until working a bit more with MyLifeOrganized. For now, I needed to access the information on my mobile, but MLO has issues with the formatted text in general. It has a basic support of HTML, so I finally export both version: a section with the plain text and a section with the formatted text. I will see while working how is that, but it would complicate a lot importing it back. These ones will be for read-only on mobile...
Apart from that, I found a couple of issues on ToDoList you might be interested:
The AddItem function seems to have an issue when creating nodes without information. It dumps a "0" where it shouldn't. It might be due to an overload of the function, instead of taking the empty string it migh be taking null instead as an integer and dumping a zero. Here it is a sample extract from one of my TDL files:
</COMMENTS>0</TASK>0</TASK>
So I fixed that in the MLOExport by calling with an empty string _T("") :
CXmlItem* pXIDestCustomFormatItem = pXIDestItem->AddItem(_T("CustomFormat"), _T(""), XIT_ELEMENT);
The other issue I detected is that, when changing the format in the combo-box, from RTF to HTML, the text is removed instead of converted from one format to the other... although that might be intentional...
Thank you very much for your support!
|
|
|
|
|
Quote: The AddItem function seems to have an issue when creating nodes without information. It dumps a "0" where it shouldn't
Thx, I'll take a look.
Quote: The other issue I detected is that, when changing the format in the combo-box, from RTF to HTML, the text is removed instead of converted from one format to the other... although that might be intentional...
Yes, this is a weakness of any plugin system because neither the app nor the plugin has any awareness of any other comments formats.
I could possibly experiment with passing the custom content of one plugin to another plugin and allow the plugin to either return FALSE or to do its own conversion but that would create interdependencies which the plugin system is trying to avoid.
|
|
|
|
|
Alternatively, if mlo supports HTML notes you could:
1. Return 'true' from CMLOExporter::SupportsHtmlComments()
2. Get the html comments using
CString sHtml = pTasks->GetTaskAttribute(hTask, L"HTMLCOMMENTS")
3. Base64 decode as before
PS. Not sure why the ITaskList interface doesn't provide this more easily. If you have any trouble getting this to work, I'm happy to help out.
|
|
|
|
|
This was the key to solve my problem, I finally used this approach! I really appreciate your help, thank you very much indeed!!!
I also improved a bit the implementation of the dependencies, making sure the GUIDs that I generate for MLO are unique, even if I mix several TDL files. Well, it is not the best of my code, I needed to implement it as quickly as I could... So, feel free to change anything if you finally want to use it! I also might have overused the notes section in MLO, as I added there the links, dependencies (actually unnecessary because they are also implemented natively for MLO, but just wanted a doble check for my personal build), and also I finally dumped the plain text and HTML versions of the comments (due to some limitations of the HTML support in MLO). So, you might want to change the criteria I finally used for my build...
Here it is the final code:
bool SupportsHtmlComments() const { return true; }
#include "stdafx.h"
#include "MLOImport.h"
#include "MLOExporter.h"
#include "..\shared\xmlfileex.h"
#include "..\shared\datehelper.h"
#include "..\shared\timehelper.h"
#include "..\shared\misc.h"
#include "..\3rdParty\T64Utils.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
CMLOExporter::CMLOExporter()
{
m_icon.Load(IDI_MYLIFEORGANISED);
}
CMLOExporter::~CMLOExporter()
{
}
void CMLOExporter::SetLocalizer(ITransText* )
{
}
IIMPORTEXPORT_RESULT CMLOExporter::Export(const ITaskList* pSrcTaskFile, LPCTSTR szDestFilePath, DWORD , IPreferences* , LPCTSTR )
{
const ITASKLISTBASE* pTasks = GetITLInterface<ITASKLISTBASE>(pSrcTaskFile, IID_TASKLISTBASE);
if (pTasks == NULL)
{
ASSERT(0);
return IIER_BADINTERFACE;
}
CXmlFile fileDest(_T("MyLifeOrganized-xml"));
fileDest.SetXmlHeader(DEFAULT_UTF8_HEADER);
fileDest.AddItem(_T("ver"), _T("1.2"));
CXmlItem* pXITasks = fileDest.AddItem(_T("TaskTree"));
if (!ExportTask(pTasks, pSrcTaskFile->GetFirstTask(), pXITasks, TRUE))
return IIER_SOMEFAILED;
ExportPlaces(pTasks, fileDest.Root());
if (!fileDest.Save(szDestFilePath, SFEF_UTF8WITHOUTBOM))
return IIER_BADFILE;
return IIER_SUCCESS;
}
IIMPORTEXPORT_RESULT CMLOExporter::Export(const IMultiTaskList* pSrcTaskFile, LPCTSTR szDestFilePath, DWORD , IPreferences* , LPCTSTR )
{
CXmlFile fileDest(_T("MyLifeOrganized-xml"));
fileDest.SetXmlHeader(DEFAULT_UTF8_HEADER);
fileDest.AddItem(_T("ver"), _T("1.2"));
CXmlItem* pXITasks = fileDest.AddItem(_T("TaskTree"));
for (int nTaskList = 0; nTaskList < pSrcTaskFile->GetTaskListCount(); nTaskList++)
{
const ITASKLISTBASE* pTasks = GetITLInterface<ITASKLISTBASE>(pSrcTaskFile->GetTaskList(nTaskList), IID_TASKLISTBASE);
if (pTasks == NULL)
{
ASSERT(0);
return IIER_BADINTERFACE;
}
if (!ExportTask(pTasks, pTasks->GetFirstTask(), pXITasks, TRUE))
return IIER_SOMEFAILED;
ExportPlaces(pTasks, fileDest.Root());
}
if (!fileDest.Save(szDestFilePath, SFEF_UTF8WITHOUTBOM))
return IIER_BADFILE;
return IIER_SUCCESS;
}
bool CMLOExporter::ExportTask(const ITASKLISTBASE* pSrcTaskFile, HTASKITEM hTask,
CXmlItem* pXIDestParent, BOOL bAndSiblings)
{
if (!hTask)
return true;
CXmlItem* pXIDestItem = pXIDestParent->AddItem(_T("TaskNode"));
if (!pXIDestItem)
return false;
pXIDestItem->AddItem(_T("Caption"), pSrcTaskFile->GetTaskTitle(hTask));
int nPriority = pSrcTaskFile->GetTaskPriority(hTask, FALSE);
int nImportance = max(nPriority, 0) * 20;
pXIDestItem->AddItem(_T("Importance"), nImportance, XIT_ELEMENT);
CString strIDD;
unsigned long nIDD = pSrcTaskFile->GetTaskID(hTask);
CString strTitle = pSrcTaskFile->GetTaskTitle(hTask);
unsigned long nTaskNameCode = strTitle.GetLength();
if (nTaskNameCode > 1)
nTaskNameCode *= int(strTitle[1]);
strIDD.Format(_T("{%08lu-0000-1111-0000-%012lu}"), nIDD, nTaskNameCode);
pXIDestItem->AddItem(_T("IDD"), strIDD, XIT_ELEMENT);
CString strNote;
int nDependencies = pSrcTaskFile->GetTaskDependencyCount(hTask);
if (nDependencies > 0)
{
CXmlItem* pXIDestDependenciesItem = pXIDestItem->AddItem(_T("Dependency"), _T(""), XIT_ELEMENT);
if (!pXIDestDependenciesItem)
return false;
CString strUID;
CString strDependencyCount;
for (int nDependency = 0; nDependency < nDependencies; ++nDependency)
{
LPCWSTR pStrDependency = pSrcTaskFile->GetTaskDependency(hTask, nDependency);
strDependencyCount.Format(_T("%d"), nDependency + 1);
strNote += _T("ToDoList Dependency (") + strDependencyCount + _T("): [ID " + CString(pStrDependency) + _T("] "));
unsigned long nID = _wtoi(pStrDependency);
HTASKITEM hTaskDependency = pSrcTaskFile->FindTask(nID);
if (hTaskDependency)
{
unsigned long nUID = pSrcTaskFile->GetTaskID(hTaskDependency);
CString strDependencyTitle = pSrcTaskFile->GetTaskTitle(hTaskDependency);
unsigned long nTaskDependencyNameCode = strDependencyTitle.GetLength();
if (nTaskDependencyNameCode > 1)
nTaskDependencyNameCode *= int(strDependencyTitle[1]);
strUID.Format(_T("{%08lu-0000-1111-0000-%012lu}"), nUID, nTaskDependencyNameCode);
pXIDestDependenciesItem->AddItem(_T("UID"), strUID, XIT_ELEMENT);
strNote += strDependencyTitle;
}
strNote += _T("\r\n\r\n");
}
}
time64_t tDate = T64Utils::T64_NULL;
if (pSrcTaskFile->GetTaskCreationDate64(hTask, tDate))
pXIDestItem->AddItem(_T("Created"), FormatDate(tDate), XIT_ELEMENT);
if (pSrcTaskFile->GetTaskDoneDate64(hTask, tDate))
pXIDestItem->AddItem(_T("CompletionDateTime"), FormatDate(tDate), XIT_ELEMENT);
if (pSrcTaskFile->GetTaskDueDate64(hTask, false, tDate))
pXIDestItem->AddItem(_T("DueDateTime"), FormatDate(tDate), XIT_ELEMENT);
TDC_UNITS nUnits;
double dTimeEst = pSrcTaskFile->GetTaskTimeEstimate(hTask, nUnits, FALSE);
if (dTimeEst > 0.0)
{
TH_UNITS nTHUnits = MapUnitsToTHUnits(nUnits);
pXIDestItem->AddItem(_T("EstimateMax"), CTimeHelper().Convert(dTimeEst, nTHUnits, THU_DAYS), XIT_ELEMENT);
}
CString strLink;
static const CString urlProtocol = _T("://");
static const CString fileProtocol = _T("file://");
int nLinks = pSrcTaskFile->GetTaskFileLinkCount(hTask);
if (nLinks > 1)
{
CString strLinkCount;
for (int nLinkCount = 0; nLinkCount < nLinks; ++nLinkCount)
{
strLink = pSrcTaskFile->GetTaskFileLink(hTask, nLinkCount);
strLinkCount.Format(_T("%d"), nLinkCount+1);
strNote += _T("ToDoList Link (") + strLinkCount + _T("): <");
if (strLink.Find(urlProtocol) < 0)
strNote += fileProtocol;
strNote += CString(strLink) + _T(">\r\n\r\n");
}
}
else
{
strLink = pSrcTaskFile->GetTaskFileLinkPath(hTask);
if (!Misc::IsEmpty(strLink))
{
strNote += _T("ToDoList Link: <");
if (strLink.Find(urlProtocol) < 0)
strNote += fileProtocol;
strNote += CString(strLink) + _T(">\r\n\r\n");
}
}
LPCTSTR szComments = pSrcTaskFile->GetTaskComments(hTask);
CString strHtmlComments = pSrcTaskFile->GetTaskAttribute(hTask, TDCA_HTMLCOMMENTS);
CString strCommentsType;
strHtmlComments.Trim();
bool bHasFormattedComments = !strHtmlComments.IsEmpty();
if (bHasFormattedComments)
{
bool bIsRTFContent = (strHtmlComments.Find(_T("<div class=WordSection1>")) >= 0);
if (bIsRTFContent)
{
strHtmlComments.Replace(_T("<div class=WordSection1>"), _T(""));
strHtmlComments.Replace(_T("</div>"), _T(""));
strHtmlComments.Replace(_T(" style='font-size:8.0pt;font-family:"), _T(""));
strHtmlComments.Replace(_T("\"Arial\",\"sans-serif\"'"), _T(""));
strHtmlComments.Replace(_T(" lang=EN-GB"), _T(""));
strHtmlComments.Replace(_T(" class=MsoNormal style='margin-top:0cm;margin-bottom:.0001pt;line-height:"), _T(""));
strHtmlComments.Replace(_T("normal;text-autospace:none'"), _T(""));
strHtmlComments.Replace(_T("<p\r\n>"), _T("<p>"));
strHtmlComments.Replace(_T("<p\n>"), _T("<p>"));
strHtmlComments.Replace(_T("<span\r\n>"), _T("<span>"));
strHtmlComments.Replace(_T("<span\n>"), _T("<span>"));
strCommentsType = _T("RTF"); }
else
strCommentsType = _T("HTML");
strNote += _T("ToDoList Comments (from ") + strCommentsType + _T("): *** To see format in notes, activate \"Use Markdown format in notes\" in the MyLifeOrganized options ***\r\n\r\n");
}
if ((!Misc::IsEmpty(strNote)) && (!Misc::IsEmpty(szComments)))
strNote += _T("\r\n\r\n");
if (!Misc::IsEmpty(szComments))
{
if (!strHtmlComments.IsEmpty())
strNote += _T("----- *** ToDoList Plain Text (HTML below) *** -----\r\n\r\n");
strNote += szComments;
}
if (!strHtmlComments.IsEmpty())
strNote += _T("\r\n\r\n\r\n----- *** ToDoList HTML (from ") + strCommentsType + _T(") *** (MyLifeOrganized supports only basic HTML formatting and Markdown) -----\r\n\r\n") + strHtmlComments;
if (!strNote.IsEmpty())
pXIDestItem->AddItem(_T("Note"), strNote, XIT_ELEMENT);
if ((pSrcTaskFile->GetFirstTask(hTask) != nullptr)) {
if (strNote.IsEmpty())
pXIDestItem->AddItem(_T("HideInToDoThisTask"), -1, XIT_ELEMENT); else
pXIDestItem->AddItem(_T("IsProject"), -1, XIT_ELEMENT); }
bool bTaskFlagged = pSrcTaskFile->IsTaskFlagged(hTask, false);
if (bTaskFlagged)
pXIDestItem->AddItem(_T("Flag"), _T("Red Flag"), XIT_ELEMENT);
CXmlItem* pXIDestCustomFormatItem = pXIDestItem->AddItem(_T("CustomFormat"), _T(""), XIT_ELEMENT);
if (!pXIDestCustomFormatItem)
return false;
bool bIsParentTask = pSrcTaskFile->IsTaskParent(hTask);
if (bIsParentTask)
pXIDestCustomFormatItem->AddItem(_T("Bold"), _T("1"), XIT_ELEMENT);
unsigned long unTaskColor = pSrcTaskFile->GetTaskColor(hTask);
CString strColor;
strColor.Format(_T("%lu"), unTaskColor);
pXIDestCustomFormatItem->AddItem(_T("FontColor"), strColor, XIT_ELEMENT);
pXIDestCustomFormatItem->AddItem(_T("SideBarColor"), strColor, XIT_ELEMENT);
if (!ExportTask(pSrcTaskFile, pSrcTaskFile->GetFirstTask(hTask), pXIDestItem, TRUE))
return false;
if (bAndSiblings)
{
HTASKITEM hSibling = pSrcTaskFile->GetNextTask(hTask);
while (hSibling)
{
if (!ExportTask(pSrcTaskFile, hSibling, pXIDestParent, FALSE))
return false;
hSibling = pSrcTaskFile->GetNextTask(hSibling);
}
}
return true;
}
CString CMLOExporter::FormatDate(time64_t tDate)
{
COleDateTime date = CDateHelper::GetDate(tDate);
return CDateHelper::FormatDate(date, DHFD_ISO | DHFD_TIME, 'T');
}
void CMLOExporter::BuildPlacesMap(const ITASKLISTBASE* pSrcTaskFile, HTASKITEM hTask,
CMapStringToString& mapPlaces, BOOL bAndSiblings)
{
if (!hTask)
return;
int nCat = pSrcTaskFile->GetTaskCategoryCount(hTask);
while (nCat--)
{
CString sCat = pSrcTaskFile->GetTaskCategory(hTask, nCat);
CString sCatUpper(sCat);
sCat.MakeUpper();
mapPlaces[sCatUpper] = sCat;
}
BuildPlacesMap(pSrcTaskFile, pSrcTaskFile->GetFirstTask(hTask), mapPlaces, TRUE);
if (bAndSiblings)
{
HTASKITEM hSibling = pSrcTaskFile->GetNextTask(hTask);
while (hSibling)
{
BuildPlacesMap(pSrcTaskFile, hSibling, mapPlaces, FALSE);
hSibling = pSrcTaskFile->GetNextTask(hSibling);
}
}
}
void CMLOExporter::ExportPlaces(const ITASKLISTBASE* pSrcTaskFile, CXmlItem* pDestPrj)
{
CMapStringToString mapPlaces;
BuildPlacesMap(pSrcTaskFile, pSrcTaskFile->GetFirstTask(), mapPlaces, TRUE);
if (mapPlaces.GetCount())
{
CXmlItem* pXIResources = pDestPrj->AddItem(_T("PlacesList"));
CString sPlace, sPlaceUpper;
POSITION pos = mapPlaces.GetStartPosition();
while (pos)
{
mapPlaces.GetNextAssoc(pos, sPlaceUpper, sPlace);
CXmlItem* pXIRes = pXIResources->AddItem(_T("TaskPlace"));
if (pXIRes)
pXIRes->AddItem(_T("Caption"), sPlace);
}
}
}
TH_UNITS CMLOExporter::MapUnitsToTHUnits(TDC_UNITS nUnits)
{
switch (nUnits)
{
case TDCU_MINS: return THU_MINS;
case TDCU_HOURS: return THU_HOURS;
case TDCU_DAYS: return THU_DAYS;
case TDCU_WEEKDAYS: return THU_WEEKDAYS;
case TDCU_WEEKS: return THU_WEEKS;
case TDCU_MONTHS: return THU_MONTHS;
case TDCU_YEARS: return THU_YEARS;
}
ASSERT(0);
return THU_NULL;
}
It was extremelly useful for me, hope it is useful some someone else too!
Thank you for your support!
|
|
|
|
|
These HTML artifacts are a consequence of the app using MS Word to convert the RTF to HTML, something that can be changed via the 'Rich Text' comments 'Preferences' dialog. Might be worth a try...
|
|
|
|
|
That is interesting... For now, I have already migrated my 12 ToDoList projects into MyLifeOrganized, and everything is going smooth and fine... even the links open, because they keep the same path on the disk! I keep the TDL files because ToDoList has more options, like Gantt, Kanban, graphs,... that MyLifeOrganized doesn't have... I migrated just to be able to see the task on my mobile smoothly...
|
|
|
|
|
ps. I don't know if you have used GitHub before, but if you are interested you could fork the main repo[^] and add your code there which would allow me to more easily review the changes you have made and decide which ones are appropriate for incorporating back into the main branch.
This would also make it simpler for you to retrieve any fixes I make in the main repo.
No pressure.
|
|
|
|
|
I use a different repository system at work, but have already used Git and Bitbucket... It is time to open an account on GitHub, no problem!
|
|
|
|
|
Alternatively, you could just email the .h/.cpp files to:
todolist.forums@abstractspoon.com Thanks.
|
|
|
|
|
Thank you very much. No problem, I will do the fork. I have been a bit busy these days...
|
|
|
|
|
Sorry for the delay, I have done the fork but will have to push the changes after my holidays. I think I also will implement the import from MLO, as I have found a bug in the MLO markdown... I contacted their support, which is very kind, but I am not sure how much will it take for them to fix. So I think it will be a good idea to keep my projects in sync from the very beginning...
|
|
|
|
|
That's great. Thanks for the update Hugo.
|
|
|
|
|
Hello! After exporting my ToDoList projects to MyLifeOrganized... I imported them into MLO and found that a lot of information was totally missing, for example all the notes/comments on the tasks... Although the notes/comments were exported into the .ML/.XML file from TDL, that information was missing in MLO. I contacted the MLO support and they told me that the format should exactly match what they are currently exporting. It seems they broke the compatibility with older versions of their own files! So MLO import is broken and can no longer properly import the older MLO format. So TDL export is no longer working fine for MLO...
It seems they changed the attribute
<TaskNode Note="..."> for another format:
<TaskNode><Note>...</Note></TaskNode>
Another thing I found as a very serious issue for me was loosing the TDL links. MLO has no similar field for the TDL links... And I have a lot of those links in my TDL projects, so: I was not accepting loosing the links nor the notes/comments of my tasks... and decided to fix the source code (thanks Dan for opensourcing!) myself! I decided to get the link information and put it at the beginning of the notes/comments. It would be nice if this can be improved also exporting the text format, the text markdown that MLO supports, task colors, flags, completion, and other info... maybe I will update the fix, but, in case you are interested, here it is my customization to the CMLOExporter::ExportTask funtion:
Instead of:
LPCTSTR szComments = pSrcTaskFile->GetTaskComments(hTask);
if (!Misc::IsEmpty(szComments))
pXIDestItem->AddItem(_T("Note"), szComments);
I wrote the following, in case you are interested:
LPCTSTR szLink = pSrcTaskFile->GetTaskFileLinkPath(hTask);
LPCTSTR szComments = pSrcTaskFile->GetTaskComments(hTask);
CString strNote;
if (!Misc::IsEmpty(szLink))
strNote += _T("ToDoList link: ") + CString(szLink);
if ((!Misc::IsEmpty(szLink)) && (!Misc::IsEmpty(szComments)))
strNote += _T("\n\n");
if (!Misc::IsEmpty(szComments))
strNote += szComments;
if (!strNote.IsEmpty())
pXIDestItem->AddItem(_T("Note"), strNote, XIT_ELEMENT);
Ideally, I would like to keep using both software, so it would be really nice to keep this exporting up to date, and also the other way around, importing from MLO into TDL...
Also, recorded a quick video about this issue: My experience migrating from ToDoList to MyLifeOrganized - YouTube[^]
Thank you for your interest!
EDIT: Check my own reply below for a very improved version of the code, exporting a lot more data...
modified 9-Aug-22 19:22pm.
|
|
|
|
|
I have improved the export to MyLifeOrganized, now I export:
- Task colors
- Flag state
- Completion state
- Comments in plain text and in HTML format! RTF / rich text comments are also exported in HTML
- Link or multiple links
- Task dependency or multiple dependencies
- First level task exported in bold, like in ToDoList
- Folders and projects: These are not available in ToDoList, but I set a criteria for setting those in MyLifeOrganized
- Fixed importance scale
- Fixed dates
Here it is the final code (that might apply to ToDoList 8.1 only, later versions might include Dan's improvements):
bool SupportsHtmlComments() const { return true; }
#include "stdafx.h"
#include "MLOImport.h"
#include "MLOExporter.h"
#include "..\shared\xmlfileex.h"
#include "..\shared\datehelper.h"
#include "..\shared\timehelper.h"
#include "..\shared\misc.h"
#include "..\3rdParty\T64Utils.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
CMLOExporter::CMLOExporter()
{
m_icon.Load(IDI_MYLIFEORGANISED);
}
CMLOExporter::~CMLOExporter()
{
}
void CMLOExporter::SetLocalizer(ITransText* )
{
}
IIMPORTEXPORT_RESULT CMLOExporter::Export(const ITaskList* pSrcTaskFile, LPCTSTR szDestFilePath, DWORD , IPreferences* , LPCTSTR )
{
const ITASKLISTBASE* pTasks = GetITLInterface<ITASKLISTBASE>(pSrcTaskFile, IID_TASKLISTBASE);
if (pTasks == NULL)
{
ASSERT(0);
return IIER_BADINTERFACE;
}
CXmlFile fileDest(_T("MyLifeOrganized-xml"));
fileDest.SetXmlHeader(DEFAULT_UTF8_HEADER);
fileDest.AddItem(_T("ver"), _T("1.2"));
CXmlItem* pXITasks = fileDest.AddItem(_T("TaskTree"));
if (!ExportTask(pTasks, pSrcTaskFile->GetFirstTask(), pXITasks, TRUE))
return IIER_SOMEFAILED;
ExportPlaces(pTasks, fileDest.Root());
if (!fileDest.Save(szDestFilePath, SFEF_UTF8WITHOUTBOM))
return IIER_BADFILE;
return IIER_SUCCESS;
}
IIMPORTEXPORT_RESULT CMLOExporter::Export(const IMultiTaskList* pSrcTaskFile, LPCTSTR szDestFilePath, DWORD , IPreferences* , LPCTSTR )
{
CXmlFile fileDest(_T("MyLifeOrganized-xml"));
fileDest.SetXmlHeader(DEFAULT_UTF8_HEADER);
fileDest.AddItem(_T("ver"), _T("1.2"));
CXmlItem* pXITasks = fileDest.AddItem(_T("TaskTree"));
for (int nTaskList = 0; nTaskList < pSrcTaskFile->GetTaskListCount(); nTaskList++)
{
const ITASKLISTBASE* pTasks = GetITLInterface<ITASKLISTBASE>(pSrcTaskFile->GetTaskList(nTaskList), IID_TASKLISTBASE);
if (pTasks == NULL)
{
ASSERT(0);
return IIER_BADINTERFACE;
}
if (!ExportTask(pTasks, pTasks->GetFirstTask(), pXITasks, TRUE))
return IIER_SOMEFAILED;
ExportPlaces(pTasks, fileDest.Root());
}
if (!fileDest.Save(szDestFilePath, SFEF_UTF8WITHOUTBOM))
return IIER_BADFILE;
return IIER_SUCCESS;
}
bool CMLOExporter::ExportTask(const ITASKLISTBASE* pSrcTaskFile, HTASKITEM hTask,
CXmlItem* pXIDestParent, BOOL bAndSiblings)
{
if (!hTask)
return true;
CXmlItem* pXIDestItem = pXIDestParent->AddItem(_T("TaskNode"));
if (!pXIDestItem)
return false;
pXIDestItem->AddItem(_T("Caption"), pSrcTaskFile->GetTaskTitle(hTask));
int nPriority = pSrcTaskFile->GetTaskPriority(hTask, FALSE);
int nImportance = max(nPriority, 0) * 20;
pXIDestItem->AddItem(_T("Importance"), nImportance, XIT_ELEMENT);
CString strIDD;
unsigned long nIDD = pSrcTaskFile->GetTaskID(hTask);
CString strTitle = pSrcTaskFile->GetTaskTitle(hTask);
unsigned long nTaskNameCode = strTitle.GetLength();
if (nTaskNameCode > 1)
nTaskNameCode *= int(strTitle[1]);
strIDD.Format(_T("{%08lu-0000-1111-0000-%012lu}"), nIDD, nTaskNameCode);
pXIDestItem->AddItem(_T("IDD"), strIDD, XIT_ELEMENT);
CString strNote;
int nDependencies = pSrcTaskFile->GetTaskDependencyCount(hTask);
if (nDependencies > 0)
{
CXmlItem* pXIDestDependenciesItem = pXIDestItem->AddItem(_T("Dependency"), _T(""), XIT_ELEMENT);
if (!pXIDestDependenciesItem)
return false;
CString strUID;
CString strDependencyCount;
for (int nDependency = 0; nDependency < nDependencies; ++nDependency)
{
LPCWSTR pStrDependency = pSrcTaskFile->GetTaskDependency(hTask, nDependency);
strDependencyCount.Format(_T("%d"), nDependency + 1);
strNote += _T("ToDoList Dependency (") + strDependencyCount + _T("): [ID " + CString(pStrDependency) + _T("] "));
unsigned long nID = _wtoi(pStrDependency);
HTASKITEM hTaskDependency = pSrcTaskFile->FindTask(nID);
if (hTaskDependency)
{
unsigned long nUID = pSrcTaskFile->GetTaskID(hTaskDependency);
CString strDependencyTitle = pSrcTaskFile->GetTaskTitle(hTaskDependency);
unsigned long nTaskDependencyNameCode = strDependencyTitle.GetLength();
if (nTaskDependencyNameCode > 1)
nTaskDependencyNameCode *= int(strDependencyTitle[1]);
strUID.Format(_T("{%08lu-0000-1111-0000-%012lu}"), nUID, nTaskDependencyNameCode);
pXIDestDependenciesItem->AddItem(_T("UID"), strUID, XIT_ELEMENT);
strNote += strDependencyTitle;
}
strNote += _T("\r\n\r\n");
}
}
time64_t tDate = T64Utils::T64_NULL;
if (pSrcTaskFile->GetTaskCreationDate64(hTask, tDate))
pXIDestItem->AddItem(_T("Created"), FormatDate(tDate), XIT_ELEMENT);
if (pSrcTaskFile->GetTaskDoneDate64(hTask, tDate))
pXIDestItem->AddItem(_T("CompletionDateTime"), FormatDate(tDate), XIT_ELEMENT);
if (pSrcTaskFile->GetTaskDueDate64(hTask, false, tDate))
pXIDestItem->AddItem(_T("DueDateTime"), FormatDate(tDate), XIT_ELEMENT);
TDC_UNITS nUnits;
double dTimeEst = pSrcTaskFile->GetTaskTimeEstimate(hTask, nUnits, FALSE);
if (dTimeEst > 0.0)
{
TH_UNITS nTHUnits = MapUnitsToTHUnits(nUnits);
pXIDestItem->AddItem(_T("EstimateMax"), CTimeHelper().Convert(dTimeEst, nTHUnits, THU_DAYS), XIT_ELEMENT);
}
CString strLink;
static const CString urlProtocol = _T("://");
static const CString fileProtocol = _T("file://");
int nLinks = pSrcTaskFile->GetTaskFileLinkCount(hTask);
if (nLinks > 1)
{
CString strLinkCount;
for (int nLinkCount = 0; nLinkCount < nLinks; ++nLinkCount)
{
strLink = pSrcTaskFile->GetTaskFileLink(hTask, nLinkCount);
strLinkCount.Format(_T("%d"), nLinkCount+1);
strNote += _T("ToDoList Link (") + strLinkCount + _T("): <");
if (strLink.Find(urlProtocol) < 0)
strNote += fileProtocol;
strNote += CString(strLink) + _T(">\r\n\r\n");
}
}
else
{
strLink = pSrcTaskFile->GetTaskFileLinkPath(hTask);
if (!Misc::IsEmpty(strLink))
{
strNote += _T("ToDoList Link: <");
if (strLink.Find(urlProtocol) < 0)
strNote += fileProtocol;
strNote += CString(strLink) + _T(">\r\n\r\n");
}
}
LPCTSTR szComments = pSrcTaskFile->GetTaskComments(hTask);
CString strHtmlComments = pSrcTaskFile->GetTaskAttribute(hTask, TDCA_HTMLCOMMENTS);
CString strCommentsType;
strHtmlComments.Trim();
bool bHasFormattedComments = !strHtmlComments.IsEmpty();
if (bHasFormattedComments)
{
bool bIsRTFContent = (strHtmlComments.Find(_T("<div class=WordSection1>")) >= 0);
if (bIsRTFContent)
{
strHtmlComments.Replace(_T("<div class=WordSection1>"), _T(""));
strHtmlComments.Replace(_T("</div>"), _T(""));
strHtmlComments.Replace(_T(" style='font-size:8.0pt;font-family:"), _T(""));
strHtmlComments.Replace(_T("\"Arial\",\"sans-serif\"'"), _T(""));
strHtmlComments.Replace(_T(" lang=EN-GB"), _T(""));
strHtmlComments.Replace(_T(" class=MsoNormal style='margin-top:0cm;margin-bottom:.0001pt;line-height:"), _T(""));
strHtmlComments.Replace(_T("normal;text-autospace:none'"), _T(""));
strHtmlComments.Replace(_T("<p\r\n>"), _T("<p>"));
strHtmlComments.Replace(_T("<p\n>"), _T("<p>"));
strHtmlComments.Replace(_T("<span\r\n>"), _T("<span>"));
strHtmlComments.Replace(_T("<span\n>"), _T("<span>"));
strCommentsType = _T("RTF"); }
else
strCommentsType = _T("HTML");
strNote += _T("ToDoList Comments (from ") + strCommentsType + _T("): *** To see format in notes, activate \"Use Markdown format in notes\" in the MyLifeOrganized options ***\r\n\r\n");
}
if ((!Misc::IsEmpty(strNote)) && (!Misc::IsEmpty(szComments)))
strNote += _T("\r\n\r\n");
if (!Misc::IsEmpty(szComments))
{
if (!strHtmlComments.IsEmpty())
strNote += _T("----- *** ToDoList Plain Text (HTML below) *** -----\r\n\r\n");
strNote += szComments;
}
if (!strHtmlComments.IsEmpty())
strNote += _T("\r\n\r\n\r\n----- *** ToDoList HTML (from ") + strCommentsType + _T(") *** (MyLifeOrganized supports only basic HTML formatting and Markdown) -----\r\n\r\n") + strHtmlComments;
if (!strNote.IsEmpty())
pXIDestItem->AddItem(_T("Note"), strNote, XIT_ELEMENT);
if ((pSrcTaskFile->GetFirstTask(hTask) != nullptr)) {
if (strNote.IsEmpty())
pXIDestItem->AddItem(_T("HideInToDoThisTask"), -1, XIT_ELEMENT); else
pXIDestItem->AddItem(_T("IsProject"), -1, XIT_ELEMENT); }
bool bTaskFlagged = pSrcTaskFile->IsTaskFlagged(hTask, false);
if (bTaskFlagged)
pXIDestItem->AddItem(_T("Flag"), _T("Red Flag"), XIT_ELEMENT);
CXmlItem* pXIDestCustomFormatItem = pXIDestItem->AddItem(_T("CustomFormat"), _T(""), XIT_ELEMENT);
if (!pXIDestCustomFormatItem)
return false;
bool bIsParentTask = pSrcTaskFile->IsTaskParent(hTask);
if (bIsParentTask)
pXIDestCustomFormatItem->AddItem(_T("Bold"), _T("1"), XIT_ELEMENT);
unsigned long unTaskColor = pSrcTaskFile->GetTaskColor(hTask);
CString strColor;
strColor.Format(_T("%lu"), unTaskColor);
pXIDestCustomFormatItem->AddItem(_T("FontColor"), strColor, XIT_ELEMENT);
pXIDestCustomFormatItem->AddItem(_T("SideBarColor"), strColor, XIT_ELEMENT);
if (!ExportTask(pSrcTaskFile, pSrcTaskFile->GetFirstTask(hTask), pXIDestItem, TRUE))
return false;
if (bAndSiblings)
{
HTASKITEM hSibling = pSrcTaskFile->GetNextTask(hTask);
while (hSibling)
{
if (!ExportTask(pSrcTaskFile, hSibling, pXIDestParent, FALSE))
return false;
hSibling = pSrcTaskFile->GetNextTask(hSibling);
}
}
return true;
}
CString CMLOExporter::FormatDate(time64_t tDate)
{
COleDateTime date = CDateHelper::GetDate(tDate);
return CDateHelper::FormatDate(date, DHFD_ISO | DHFD_TIME, 'T');
}
void CMLOExporter::BuildPlacesMap(const ITASKLISTBASE* pSrcTaskFile, HTASKITEM hTask,
CMapStringToString& mapPlaces, BOOL bAndSiblings)
{
if (!hTask)
return;
int nCat = pSrcTaskFile->GetTaskCategoryCount(hTask);
while (nCat--)
{
CString sCat = pSrcTaskFile->GetTaskCategory(hTask, nCat);
CString sCatUpper(sCat);
sCat.MakeUpper();
mapPlaces[sCatUpper] = sCat;
}
BuildPlacesMap(pSrcTaskFile, pSrcTaskFile->GetFirstTask(hTask), mapPlaces, TRUE);
if (bAndSiblings)
{
HTASKITEM hSibling = pSrcTaskFile->GetNextTask(hTask);
while (hSibling)
{
BuildPlacesMap(pSrcTaskFile, hSibling, mapPlaces, FALSE);
hSibling = pSrcTaskFile->GetNextTask(hSibling);
}
}
}
void CMLOExporter::ExportPlaces(const ITASKLISTBASE* pSrcTaskFile, CXmlItem* pDestPrj)
{
CMapStringToString mapPlaces;
BuildPlacesMap(pSrcTaskFile, pSrcTaskFile->GetFirstTask(), mapPlaces, TRUE);
if (mapPlaces.GetCount())
{
CXmlItem* pXIResources = pDestPrj->AddItem(_T("PlacesList"));
CString sPlace, sPlaceUpper;
POSITION pos = mapPlaces.GetStartPosition();
while (pos)
{
mapPlaces.GetNextAssoc(pos, sPlaceUpper, sPlace);
CXmlItem* pXIRes = pXIResources->AddItem(_T("TaskPlace"));
if (pXIRes)
pXIRes->AddItem(_T("Caption"), sPlace);
}
}
}
TH_UNITS CMLOExporter::MapUnitsToTHUnits(TDC_UNITS nUnits)
{
switch (nUnits)
{
case TDCU_MINS: return THU_MINS;
case TDCU_HOURS: return THU_HOURS;
case TDCU_DAYS: return THU_DAYS;
case TDCU_WEEKDAYS: return THU_WEEKDAYS;
case TDCU_WEEKS: return THU_WEEKS;
case TDCU_MONTHS: return THU_MONTHS;
case TDCU_YEARS: return THU_YEARS;
}
ASSERT(0);
return THU_NULL;
}
Hope it is useful for someone!
modified 10-Aug-22 17:41pm.
|
|
|
|
|
I'm very sorry for not having responded to this earlier Hugo.
If you can add this information to the bug report requested in my other comment, I'll be happy to get it fixed in the next update of 8.3.
|
|
|
|
|
Can MyLifeOrganized (MLO) Android app be used to display ToDoList TDL files exporting ML files to Dropbox or similar?
I have seen ToDoList can export and import in MLO format, so... it seems possible to export TDL into ML files and then use the MLO app to display them (better than the current Android app).
Is there a way to automatically export and open the ML files?
It would be really interesting to explore that possibility to have a better app been able to display the taks...
Thank you!
|
|
|
|
|
Hi Hugo
You can definitely auto-export your TDL file to MLO via the following preference:
Tools > Preferences > File Actions (more) > Saving > Automatically export after saving > Other format: My Life Organised
But then, after DropBox had synced the file to your Android device, you would need some code to detect the change and load the exported file into the MLO Android app. Which I unfortunately cannot help you with.
[Update] I've installed DropBox and MLO on my Android tablet but I can't understand how to import a .ml file. Have you figured that part out?
modified 28-Jul-22 22:20pm.
|
|
|
|
|
Hi Dan, thank you very much for your reply! The automatic export is very useful indeed. I think it is not possible to import a ML file on Android, I asked the MLO team in case they can implement that... it would be extremelly useful. The only way I found was to import it on the Windows MLO app, and them sync it with the Android app. But... I still have to search for the ML file on Android, because maybe there is a possibility to *replace* it with the one in Dropbox...
If that part were possible in the future, the issue would be re-integrating the changes in the Android's ML file, into the TDL file on Windows. As ToDoList can import from ML, it seems feasible... Although the newest ML file seems to be a binary file, not sure if TDL can read from it. Maybe it would require to export an XML from MLO or something...
I will be doing some testings, but it would be great if the MLO team could help with the integration or could help giving us access to their API...
|
|
|
|
|