Guenther Goerz has also created a Mac version of the Image Markup Tool using Wine Bottler (it's a large download). Download it from the IMT Downloads page.
Category: "Activity log"
An update to the Image Markup Tool has been released. This includes a couple of small enhancements and fixes, detailed on the Updates page of the site.
A user reported the usual Firefox bug when creating an empty <ref>
element; the empty anchor that results from this kills FF's rendering of the page. Easy enough to trap in XSLT, which I'll do.
Another report concerns the attempt by the application to fix non-well-formed XML. It will successfully escape ampersands which appear in text nodes, but fails when they're in attribute nodes (such as lengthy URLs with multiple parameters). This might or might not be fixable; I'll have to take a look.
Fixed the two problems reported in the last two posts, and provided a new version to the user who reported them. If everything works well, I'll do an official release. Putting this on as a task in case I forget.
The <ref>
element's @target attribute should be converted to a proper XHTML link on when you export to create a web view.
Interesting issue reported by a user: if you have empty p tags in your annotations, then a self-closing div is output to the HTML. If you then open the file in Firefox, it will screw up, but if you give the file an .xhtml extension, it works fine.
I should put in a trap for empty paragraphs, and insert a non-breaking space into them. That could easily be done in the XSLT. Making this a task.
The Image Markup Tool version 1.8.2.0 has been released. This is a minor bugfix release. See the Update page for more details.
Working at home on a personal project, I've found a bug in the way IMT handles the content of annotation titles (which are <head>
elements). When reading <head>
elements which contain mixed content, a space character preceding an element node is eliminated. I pinned it down and fixed it, and I've created a new release, but I'll test it at home before I release it finally.
They can read and write themselves from a QDomDocument, and seem to be working well. Next, I need to polish up the test application so that it works as a demo of that module of the real app, which means installing some more filtering features (sorting works already, but filtering on any of the values in the fields would be great).
Added transcriptional attribute, and tested sorting with QSortFilterProxyModel.
...in that I can add a link and have it show up in the connected QTableView. We're moving forward. Must remember to add the link subtype field to ImtLink, though.
Began fleshing out the ImtLinkSet class, after some trouble deciding whether to base it off QAbstractTableModel or QStandardItemModel, and whether to keep the links in a QMap or QHash (chose the former in both cases). It compiles, but doesn't have enough functionality for testing yet.
I'm still unsure about QMap; it may be that a simple QList with some lookup functions would be way better in the end.
After some experimentation, I realized my link setup was far too complicated, so I've thrown it away and started again, based on what I've learned from my home project. I've now rewritten ImtLink, and all links will be handled by a single ImtLinkSet class which will descend from QAbstractTableModel (not yet started). The end result should be much simpler, and easier to plug into the GUI.
One thing I've learned from my home project GVT is that your core data model really should descend from an QT abstract model, and not anything else, so I've started converting the top-level link-handling class, ImtLinkGrpSet, to a descendant of QAbstractTableModel. This involves re-implementing inherited functions from ImtElement, and implementing the core virtual functions from the table model. It's about half done, with one outstanding difficulty.
Interview with MT to complete a questionnaire from which she'll build a wireframe model. This was useful, and helped me to clarify a couple of ideas I haven't been sure about. I'm going to detail them here while they're fresh in my mind:
- All main windows will be dockable.
- The three main windows will be:
- The Images window, looking much like our previous mockup.
- The Links window, which will provide an accessible list of all the links made so far, organized by category.
- The document browser, which will provide access by filterable xml:id to the ids already in the document, and also by means of a tree model of the document body.
- Other windows will be modal dialogs:
- The category manager (or should this tie into the links manager?).
- The XML editor (pops up from the document browser when you elect to edit an element).
- The Preferences window.
- The Output options window (could this simply be integrated into Preferences?).
- Still haven't decided how to handle translations -- presumably the easiest mechanism would be QT's built-in translation tools, but this might require separate compilation for each language -- check this out.
- One issue which came up was accessibility: is it going to be possible to allow users to define zones using only the keyboard? It's conceivable, but it might be really difficult to design and code.
A couple of bugfixes and enhancements have been made to the Image Markup Tool, resulting in the release of version 1.8.1.9. These are the changes:
- A Hungarian interface translation was contributed by Kóta Péter.
- Pressing the Delete key when an annotation zone was selected on the image didn't do anything; now it triggers a "Delete annotation(s)" action.
- When clicking in the Annotation Title field, if the default text is still there as a guide, it should be removed automatically. However, this wasn't happening when an alternative language interface was loaded. That bug is now fixed.
The new version is available from the IMT website.
Did some work on the old IMT to create a new release, adding a feature for MB:
- You can now press Control + G or Control + Shift + G in the Annotation Text box, and get a dialog to select one or more images; for each image you select, you'll get a TEI
<graphic>
tag pointing to it, either just by file name or by relative path. - I've added a popup menu to the main image control and to the annotation list, allowing the three regular annotation actions.
- I've hooked up Edit / Delete so that it can also (offer to) delete a selected annotation.
The latter two fix some annoyances that have been bugging me for a while. Also updated the documentation date, and sent the package out to MB for testing.
Started adding some doxygen documentation to the code for the XML editor. This is a way of getting back into the project after a few weeks break.
Created a logo image (which may change, of course), and an About box class based on QDialog which retrieves application title and version information from the auto-generated version.h file, and displays it.
This is now working well, and seems to be pretty solid; I've tested it in the context of the application itself, and it returns the right values. It can also compare another version number to determine whether it's older, newer or identical. I'm still not exactly clear on how I'll integrate this with compilation on other platforms. I've added version.sh to SVN, and the other platforms could just check out and use it (turning off the pre-compilation step that generates it); however, if version.sh is in SVN, we have the slight paradox that every compile will result in a new commit, because version.sh will be renewed each time and will therefore end up being committed. Maybe that's not a bad thing, actually; that has the effect of incrementing the SVN revision number for every build that's done, making the SVN revision work similarly to a build number. I've modified the script a bit so that it does a commit first, to update any other changed files; then it builds version.h; then it commits the rebuilt version.h right at the end. That means the committed version.h will always be one version behind the actual SVN version, but that's no bad thing unless we end up editing code on the other platforms, which I'm hoping will not happen.
#!/bin/bash #Major version number (unlikely to change). major=2 #Minor version number is changeable, but done manually. minor=0 #Build number is yyyymmdd from today. date=$(date +%Y%m%d) dateDashed=$(date +%Y-%m-%d) dateSeconds=$(date +%s) #This is the file we're dealing with. outfile="version.h" #Do an SVN update, then commit to update the revision number. svn update svn commit --message "Committing any changed files before generating version.h for an application build." #Empty the file. echo "">$outfile #Get the SVN revision number into a variable, and parse out the last version number. svnrev=`svnversion | sed -e 's/.*://' -e 's/[A-Z]*$//'` #echo the info into the output file. #echo $svnrev>>$outfile #echo $major"."$minor"."$build"."$svnrev>>$outfile echo "//This is an auto-generated file, created at build time as part of a Custom Build Step.">>$outfile echo "//Do not edit this file directly. It is generated from the bash script appversion.sh.">>$outfile echo "#ifndef VERSION_H">>$outfile echo "#define VERSION_H">>$outfile echo "">>$outfile echo "">>$outfile echo "#include <QString>">>$outfile echo "#include <QStringList>">>$outfile echo "">>$outfile echo "namespace Version">>$outfile echo "{">>$outfile echo "const int MAJOR = "$major";">>$outfile echo "const int MINOR = "$minor";">>$outfile echo "const int DATE = "$date";">>$outfile echo "const int REVISION = "$svnrev";">>$outfile echo "inline const char* strVersion(){return "$'\042'$major"."$minor"."$date"."$svnrev$'\042'";}">>$outfile echo "inline const char* strVersionMajor(){return "$'\042'$major$'\042'";}">>$outfile echo "inline const char* strVersionMinor(){return "$'\042'$minor$'\042'";}">>$outfile echo "inline const char* strVersionDate(){return "$'\042'$dateDashed$'\042'";}">>$outfile echo "inline const char* strVersionRevision(){return "$'\042'$svnrev$'\042'";}">>$outfile echo "inline int intVersionMajor(){return $major;}">>$outfile echo "inline int intVersionMinor(){return $minor;}">>$outfile echo "inline int intVersionDate(){return $dateSeconds;}">>$outfile echo "inline int intVersionRevision(){return $svnrev;}">>$outfile echo "inline int versionComparedWithThis(QString &candidateVersion){">>$outfile echo "//This returns -1 if the candidate version is older, 0 if it's the same, and 1 if it's newer.">>$outfile echo -e "\t bool ok;">>$outfile echo -e "\t QStringList verList = candidateVersion.split("$'\042'"."$'\042'");">>$outfile echo -e "\t if (verList.count() < 1) return -1;">>$outfile echo -e "\t int candMajor = verList.at(0).toInt(&ok, 10);">>$outfile echo -e "\t if (candMajor < "$major") return -1;">>$outfile echo -e "\t if (candMajor > "$major") return 1;">>$outfile echo -e "\t if (verList.count() < 2) return -1;">>$outfile echo -e "\t int candMinor = verList.at(1).toInt(&ok, 10);">>$outfile echo -e "\t if (candMinor < "$minor") return -1;">>$outfile echo -e "\t if (candMinor > "$minor") return 1;">>$outfile echo -e "\t if (verList.count() < 3) return -1;">>$outfile echo -e "\t int candDate = verList.at(2).toInt(&ok, 10);">>$outfile echo -e "\t if (candDate < "$date") return -1;">>$outfile echo -e "\t if (candDate > "$date") return 1;">>$outfile echo -e "\t if (verList.count() < 4) return -1;">>$outfile echo -e "\t int candRevision = verList.at(3).toInt(&ok, 10);">>$outfile echo -e "\t if (candRevision < "$svnrev") return -1;">>$outfile echo -e "\t if (candRevision > "$svnrev") return 1;">>$outfile echo -e "\t return 0;">>$outfile echo -e "\t">>$outfile echo -e "\t">>$outfile echo -e "\t">>$outfile echo "}">>$outfile echo "">>$outfile echo "}">>$outfile echo "">>$outfile echo "">>$outfile echo "#endif // VERSION_H">>$outfile svn commit --message "Committing modified version.h before an application build."
The version information data is crucial for completing the <teiHeader>
in a useful way, and I've been puzzling over how best to do it in an automated way. Following hints from this post: http://qtcreator.blogspot.com/2009/10/generating-automatic-version-numbers.html, I've written a bash script that creates a version.h header file, which provides all the key versioning info I'll want to include. Below is the script at the moment (it will probably get a bit more complicated later). I'll also want to figure out how to include the same info into a resource file in order to embed the correct versioning info into Windows builds, based on this post: http://stackoverflow.com/questions/2784697/setting-application-info-in-qt. I run the script automatically when doing a build, to get an up-to-date version.h file. This is done using a Custom Build Step in the Build Settings panel of QT (in "Projects").
#!/bin/bash #Major version number (unlikely to change). major=2 #Minor version number is changeable, but done manually. minor=0 #Build number is yyyymmdd from today. date=$(date +%Y%m%d) #This is the file we're dealing with. outfile="version.h" #Remove the old file. rm $outfile #Do an SVN commit to update the revision number. svn commit --message "Committing before an application build." #Get the SVN revision number into a variable. svnrev=`svnversion` #echo the info into the output file. #echo $svnrev>>$outfile #echo $major"."$minor"."$build"."$svnrev>>$outfile echo "#ifndef VERSION_H">>$outfile echo "#define VERSION_H">>$outfile echo "">>$outfile echo "namespace Version">>$outfile echo "{">>$outfile echo "const int MAJOR = "$major";">>$outfile echo "const int MINOR = "$minor";">>$outfile echo "const int DATE = "$date";">>$outfile echo "const int REVISION = "$svnrev";">>$outfile echo "}">>$outfile echo "inline const char* versionString(){return "$'\042'$major"."$minor"."$date"."$svnrev$'\042'"};">>$outfile echo "">>$outfile echo "">>$outfile echo "">>$outfile echo "#endif // VERSION_H">>$outfile
This is part of making it easier to access and manage ImtLinkGrp and ImtLink objects by using a QString key. The key in this case is the linkType attribute, which we want to ensure is unique anyway.
Centralized the generation of hash values for links into a static class method of ImtLink, so that it can be called from other classes, and so that it's easy for me to tweak it if necessary. Previously there was a possibility that two different pairs of ids could accidentally result in the same hash, but I've fixed that by adding some wrapper characters.
Spent an hour working on the documentation of the core classes, running Doxygen repeatedly and fixing the errors that showed up. I think I have my documentation practices pretty much organized now, so I'll be able to go back and document the earlier parts of the project (the ones that will survive into the application).
Rewrote ImtLinkGrp so that it stores its ImtLinks in a QHash instead of in a QSet. In some ways this is more complicated, but it's a bit more robust, and enables quicker and easier lookup of a link. Modifications were needed to the ImtLink class as well.
Next is the similar task of rewriting ImtLinkSet in the same way. The advantages are more obvious and immediate there.
I've added more methods for finding and checking the existence of links in link groups and sets, and for changing the link type of a link (by moving it from one group to another). I've made other minor tweaks to code and documentation.
However, I'm beginning to question one core decision in the ImtLinkGrpSet class. I don't think that the linkGrps member should be a QSet; I think it should be a QHash. The key should be the linkType value, giving me easy access to the linkGrp and its links through the type string, and ensuring its uniqueness, which is essential. This would require substantial refactoring, but I think it's worth it.
It's also worth thinking about making the links member of the linkGrp class a QHash as well, using a concatenation of the target zone id and target text id as the key. This would have similar advantages; it would then be very easy to get a pointer to a link with its core information. I think this will be my next task.
Moved assignments in constructors into initialization lists where possible, for efficiency and speed. Also added new functions for finding a linkGrp by its linkType, and added getAllLinkTypes method to ImtLinkGrpSet, as preparation for assignment of new links to groups, and moving of links from group to group.
Reliable tracking of the "modified" (aka "dirty") status of data objects, especially those in hierarchies, is important. I've now implemented the system I outlined in the earlier post, with modified status appropriately migrating up or down the tree so that it can easily be determined at any point. I've also started writing more careful testing code.
One issue I haven't yet faced, but I'm beginning to think about, is how to manage changes such as the linkType property of a link element. I can't simply allow that to be changed, because a link may be part of a linkGrp, and in that case the linkGrp would manage the linkType; and in turn, the change of linkType actually represents a move of the link from one linkGrp to another, which itself ought to be managed at the level of the linkGrpSet. I could actually remove the linkType attribute from links altogether, on the basis that in this project, I'm never going to allow that to be used in the context of the link itself or the linkGrp, but only at the level of the linkGrpSet, but that would be rather inflexible; and it does, somehow, seem to be a property of the link when considered from the user's point of view (as it is in the current IMT, where it's shown as a category).
All this needs careful thought before I commit to a strategy.
Added a color
property and a property which is an enumeration {NONTRANSCRIPTIONAL, TRANSCRIPTIONAL} to handle the need to set the outline color for a <zone>
and the possible need to output @corresp
and @facs
attributes in addition to <linkGrp>
s.
I've also been working on plans for handling the "modified" property for this collection of classes. Ideally, I want to be able to interrogate the ImtLinkGrpSet
object to get the modified state of everything below it, but I also want to be able to set modified at any level rather than going through the hierarchy, so I'm thinking of a setModified() method for all the objects which checks to see if it has a parent; if it does, and the new setting is true, it calls the parent's method instead (thus causing true to propagate up to the top level), but if it's false, it just sets its own modified property, and all those of its children. This means I should be able to read and write at any level and get the required behaviour -- assuming that I always try to read at the highest level. Setters at every level can affect the tree upwards and downwards, depending on whether the value is true or false. It's confusing when you try to explain it, but it will work (I've done something similar, but not as well planned, in Delphi code).
Added a caption attribute to the ImtLinkGrp
class. This is the human-readable caption for the link group (as opposed to the QName-style identifier linkType
), and is stored in the @n
attribute of the <linkGrp>
tag (TEI defines this vaguely as an identifier or label, so it seems reasonable).
TODO: make the linkType
attribute conform to QName constraints.
ImtLink
, ImtLinkGrp
, and ImtLinkGrpSet
all have some properties and methods in common, so I've now derived them from an abstract base class called ImtElement
.
Built most of the ImtLinkGrpSet class, which manages a set of ImtLinkGrp objects. This involved making some modifications to ImtLinkGrp, so that it is aware of the possible existence of its parent object and is able to add and delete itself in the parent where appropriate.
Lots of work still to do on this class, much of which will require that I do more detailed planning on how exactly it will interact with the ImtDoc object. I need to thoroughly test the readFromDomElement() function, and add functions such as getLinksForZone(), getLinksForTextEl(), and getters and setters for all the relevant members of ImtLinkGrp and ImtLink.
It makes more sense to have a class which handles a <linkGrp>
element to be called ImtLinkGrp
, so I've refactored the code and changed the file names. I've also made the namespace handling more sophisticated, so if the ImtLink
or ImtLinkGrp
classes have an assigned namespace, then they use createElementNS()
to write themselves, but if not, they use createElement()
. Finally, I've made ImtLink
more sophisticated in its handling of linkType
and nsUri
properties: if it has a parent ImtLinkGrp
, then it uses the properties from that, but if not it uses its own.
This class handles a <linkGrp>
and its component <link>
elements. It's now functioning well and tested; it can read and write itself from a DOM element successfully, and provide a list of links with a particular target <zone>
, or target <text>
element. I'm gradually getting a handle on how best to do all these things.
The issue of XML namespaces remains. I had assumed that if I set all elements to the same namespace explicitly, and then output the document as a text string, I would see a sensible serialization whereby child elements with the same xmlns
as their parent would not have an explicit xmlns
attribute. Instead, I see the xmlns
attribute (I know it's not really an attribute, but there's no other practical word) on every single element.
So I'm now figuring that what I actually need to do is to make all my XML handling namespace-free, so that the root namespace for the document is set once, and all the elements that don't need an explicit namespace don't get one. That doesn't mean they have an empty one -- that just results in an empty xmlns
attribute. They need to be created with createElement()
rather than createElementNS()
.
Namespace prefixes can be handled with createElement("myNs:myElement")
and setAttribute("myNs:myAtt" "myVal")
, which is what I think I'll do with (for instance) @xml:id
. So I'll have to refactor my <linkGrp>
and <link>
code to remove the namespace handling. Annoying, but things will be simpler. The issue of how to handle possible embedded SVG or other namespaces is still not clear to me.
Completed the ImtLink
class, and documented it and the ImtConsts
file using Doxygen. I'm building everything to be namespace-aware by default, which I think is the best approach. The QDom
functionality is working well for me so far. I've now started work on the ImtLinkSet
class, which is going to use a QSet
to manage a group of links, and provide rapid lookup and i/o functionality for a TEI <linkGrp>
.
I like the idea of using TEI <link>
elements to manage the relationships between <zone>
s and <text>
elements, but there's one gotcha: they can have as many target ids as they want, and it's not clear what that means. I'm going to think more carefully about this. Using <link>
and <linkGrp>
gives me much more flexibility in categorizing and grouping links, and that's important; however, I think users will probably also want the option to output the other linking mechanisms (@corresp
and @facs
), and the question then is whether I give them both (meaning that they might get out of sync), and whether I actually try to read the other linking types, or just stick with my clean and separate <linkGrp>
s.
Created a constants class for XML regexps for name checking etc, and began work on a link modelling class, ImtLink. Slow but steady progress.
Working through one of the example applications in Blanchette and Summerfield to get a grounding in QGraphicsScene etc, before I plan the class hierarchy for IMT 2. So far so good...
Got my bits of the proposal written and sent them off to MT.
Managed to integrate my code with MJ's zone handling code, and got zones appearing on top of the test image. Created a sample popup menu to illustrate how linking would work, and then discovered that in order to take a screenshot of the app, I had to cause it to lose focus, which caused the popup menu to disappear. Ridiculous problem. Something in QT seems to be disabling the OS shortcut for screenshots.
So I had to build screenshotting into the app itself -- not too hard -- and trigger it with the onAboutToHide() event of the popup menu, so that it would trigger as I clicked on a menu item. This worked pretty well, and I have my screenshots now. Learned quite a bit in the process, though.
I need to rewrite MJ's code significantly, to hive off the controller component and to convert the idea of annotations to the more accurate concept of zones. Annotations are not part of the new structure, in the old sense; although it will be possible to create a div on the fly in the IMT when creating a zone, the more conventional path will be to link to an existing element through its xml:id; zones themselves may keep lists of pointers to the elements they're linked to, or those links may simply be handled externally by a link manager. I think I like the latter option best.
Took my first shot at trying to integrate MJ's polygon zone controls with my mock-up application, and I'm finding that the only practical approach is going to be subclassing MJ's class to add the handler which draws the pixmap in the background. I think as long as I draw the pixmap first, it should work OK, but I don't want to waste too much time on it, because it's not actually going to be the real approach we take to writing this bit of the app. Nevertheless I keep learning new stuff as I go.
I'm working on a QT mock-up which we can use for screenshots for the proposal for DH2011, and making use of the opportunity to hack around with various options for handling the graphics in the interface of the actual application. What I learned today, after many hours' work, is not to trust QT Designer at all. I could find no way to make a layout with QT Designer that would actually expand when the window containing it expanded. I spent a long time testing all the options, and nothing at all worked. As soon as I built the layout manually in the source file, it worked perfectly. In future, I think I'll use QT Designer for the basic setup of a window (menu, toolbar, status bar), but then everything inside the centralWidget I'll do manually. Much simpler.
MT and I are going to submit a proposal for DH 2011 about plans for IMT 2, so we need to create some mockups for screenshots. In the process of doing this, I can also create some new icons I'll need, and try out various configurations for the application GUI layout, so it's quite useful. I've created three new icons which look OK (although I might thicken up some of the lines a little to make them clearer at small size), and I've made some progress with basic toolbar and menu setup.
Today double-buffering in widget creation (something we might well use in IMT, for large images that need to be zoomed often), and also some gotchas about using the STL -- the sample code from the book I'm using didn't include the includes I needed. Note to self: remember #include <cstdlib>
and #include <cmath>
etc. if you want to use e.g. std::pow()
.
Managed to get some time to do some more QT learning. I've been creating custom widgets, and learning about QRegExpValidator and QMap.
Downloaded the latest QT Creator and installed it (without sudo) alongside my older ones, in my home dir. Then I found I couldn't build some of my apps, because of an error: ‘Quit’ is not a member of ‘QKeySequence’. A little Googling suggested that Creator was trying to build with an older qmake, or against an older version of QT4. The new version of QT Creator is 2.0, but it Abouts itself as "QT 4.7", although the QT4 version shows as 4.6.3, which is really confusing. In the end, I deleted the new install, and re-installed with sudo to the place it wanted to install to, in /opt. Then I specified 4.6.3 as the QT version, and I was able to build my dom application. I still don't have a QT version in my path; I'm not sure whether that's a good thing (because I often upgrade QT and have different versions on the system) or a bad thing (because QT libraries are occasionally not found, which could be the cause of various problems in building the Phonon example). We'll see...
The issue of synchronizing the two views of the document structure (list of xml:ids and tree view) is holding me up to a degree which is not commensurate with its importance. It's extremely complicated, because in fact the two views do not ultimately inherit from the same data source (the id list is constructed by parsing the document into a QStringList), so formally correct approaches to synchronizing selection using identification of equivalent QModelIndexes by tracing back to the source data won't fly, so some ad-hoc solution is going to be required. And in any case, it's by no means crucial that the two views stay in sync, at least initially. So I'm going to move on to the next stage, which will enable me to get some more code-writing experience in QT, and return to this issue at a much later stage.
The next stage is to plan the class structure that underlies the data model, by reviewing our previous plans, and starting from MJ's zone-defining code.
I'm currently re-reading all the material about model/view architecture in QT to figure out how best to approach the synchronization of two views which have two underlying models (one tree, one string list), and where the synchronization code should live (in the code for the dialog box holding the view controls, or in the HcmcImtDoc class). An added complication is that the QSortFilterProxyModel which is currently filtering the list view by regexp currently lives in the dialog box code, but I think it should almost certainly move to the HcmcImtDoc class.
Fixed the duplicate item bug in the string list filter, and fixed an unused parameter issue in the constructor of HcmcImtDoc. Also spent a bit of time narrowing down an error message when running the application: CRITICAL **: atk_object_set_name: assertion `name != NULL' failed. This turns out to be a bug in GTK/Gnome, which is Ubuntu's problem, not QT's. If you run the app with "-style windows" you don't see it. It doesn't affect anything.
The next task is to figure out how to hook together selections between the two views of the document (the tree view and the xml:id list), so that if you select something in one view, it gets selected in the other. I suspect this will be something like:
- Get the model index for the selected item, and derive the source item from it.
- Get the model index for the same item in the parallel view (if there is one).
- Set the selection in the parallel view, without triggering the reverse process.
Went back to square one and created a new QSortFilterProxyModel to filter the list of xml:ids coming from the HcmcImtDoc object. This has solved the problem; I now have a working regular expression filter in the GUI, which can be used to select items in the list.
A new problem has emerged, though: each xml:id in the list is duplicated. This is not caused by the new filter; if I change back to the original unfiltered behaviour, I still get two of each item in the list. So this is due to what looks like duplicated code in HcmcImtDoc, which I think ended up in there when I was making changes to allow switching on and off of namespace-awareness. Shouldn't be hard to fix.
Did a QT update, but the issue still remains: "source_parent was not valid", in either filterAcceptsRow or filterAcceptsColumn of HcmcIdFilter. I'll probably need to re-read all the documentation on QSortFilterProxyModel...
Still working unsuccessfully on the QSortFilterProxyModel problem, but I've now discovered that a bug in that class was fixed in QT 4.6.2, so it may be solved by an update which I'll do tomorrow.
Cleaning up dead code; preparing to re-integrate the regexp filter for the xml:id list view. I'm having trouble re-understanding some of my original code, using the QSortFilterProxyModel...
Added bool properties for namespace awareness in the HcmcImtDoc and DlgXmlIndex classes, so we don't have to decide right now which approach is best for our purposes. These are set in the constructor, but there's also a setter to change them later.
Tested two approaches to handling XML documents, one of which is namespace-aware and the other which is not. Both have advantages and disadvantages, so it's not yet clear which approach we should take for IMT. I need to make this switchable at the level of DlgXmlIndex and HcmcImtDoc, via public properties.
SA and I have decided that an effective way for Half-Baked to prepare for writing Hot Potatoes 7 would be to sponsor the development of the Image Markup Tool version 2, allowing us to develop a set of core libraries and functions, as well as gaining familiarity with the issues involved in compiling and releasing for different platforms. We also intend to use IMT 2 for creating end-user documentation in HotPot 7.
For this reason, Half-Baked will pay SA and myself to work on the development of IMT, and this work will be tracked in the "Half-Baked Contributions" post category, as well as in the "Activity Log" category.
I've started the process this morning by checking out the project and confirming I can build the DocModel subproject.
I've now created a container class which hosts a DomModel and a QStringListModel, showing two views of the same data. It should be fairly easy to hook up the filter which I had hooked up before, to allow filtering of the list view. After that, we begin to figure out how to hook together the selections. I think the string list model is actually going to have to store a reference to the corresponding DomModel items, and the DomModel items might have to store a reference to the original nodes in the DomDocument...
I'm slowly coming to the conclusion that it's NOT possible to implement two separate views of the data in the document, in the way I need to implement them. The basic issue is this:
- The
DomModel
class I'm using to create the tree model is implemented as a "lazy" system, which means that data is not provided, and indexes are not created, until they're asked for by the view. That means that only the root elements are processed initially. That, in turn, means that the proxy model is only fed the root elements to construct its list of items with@xml:id
attributes. - Even if I were able to overcome this by forcing the DomModel to fully populate itself initially, I'm still left with the problem that the way the
QSortFilterProxyModel
works makes it impossible to do what I need to do. This is because filtering is accomplished through afilterAcceptRow()
method, which you implement to return true or false. If I implement this so that it returns true only for elements with@xml:id
s, then as soon as it encounters an element which doesn't have one, it has to return false; at that point, the children of the element are not processed. I could try to determine whether each element has any descendants which have@xml:id
, but then I'd be accepting and therefore displaying a whole set of elements which don't actually have@xml:id
s, just so their descendants can display.
I think the best approach is actually going to be to implement a whole different sort of structure, with two different models, built from an underlying DOM document, but encapsulated in one object. It might be easier, then, to keep the two models in sync, especially since we're expecting to allow the document itself to change; each model could be instructed to rebuild a part of itself when a component changes. It might also be easier to manage the selection issues manually in a parent object, rather than try to use the single object. I'm planning this now.
Today I reimplemented the tree view based on a DomModel taken from the SimpleDomModel example, and hooked that up to a QTreeView, replacing the QTreeWidget. This works great. The next phase is to filter that model into QListView, which I'm assured by someone on one of the QT forums can be done by subclassing QSortFilterProxyModel, and implementing filterRows and filterColumns. This I've now done, but the results are not encouraging: nothing shows up in my list. At this point, I'm not sure whether this is because the filter method code is flawed, so it's never accepting a row, or because when a row is rejected, its children are never tested, which means that you can't get down as far as a list of elements which have @xml:ids; or even because child rows are never tested at all, so only the top level nodes get as far as the filter. At the moment, I'm tending towards the last option, because even when I make my function return true in all cases, only the top level nodes show up.
I'm sure I'm on the right track here, but it's going to take some more work...
Changed the list of xml:ids so that it's implemented with a true model based on QStringListModel, proxied through a QSortFilterProxyModel, enabling regular expression filtering. This is based on an example in Blanchette and Summerfield (pp. 250ff). The next step would be to replace the QTreeWidget with a QTreeView and build a similar model there, but I'm not quite sure how I would keep the two models in sync; the thing I don't yet understand is how, or even whether, you can have two distinct models which derive from the same underlying model, or one model which is simultaneously a QStringListModel and a custom tree model.
I'm doing the preliminary planning and reading for a document model which will provide three views on the underlying code in the TEI file:
- A read-only syntax-highlighted text view (MdhXmlEditor).
- A tree view (QTreeView).
- A string list view (QListView).
The last two will have access to right-click options to add new items, edit existing items, and presumably delete. The basic approach that seems best is outlined in this example project, which builds a tree view from a DOM model of a document. However, this example project has some key limitations that I need to work around. First of all, it's a read-only model, which means that it doesn't have to worry about updating itself when changes are made to the underlying document. Secondly, it only provides a single view of the data in a QTreeView; I need to bind the same model simultaneously to at least two different views (and probably three, if #1 above is integrated as a "view" in the model-view architecture rather than being handled in a more ad-hoc way). I now need to find more examples of multiple views, and I need to get a real handle on how views are updated when a model changes. I may even need a third view, which shows only one item and allows it to be edited; that would make things even more complicated. Perhaps the read-only view shouldn't be read-only; it should just show the selected item, and no surrounding code, and it might be the whole editing interface. You could see the whole document by clicking on the root element in the tree view.
This stuff is hard -- for me, anyway -- but it's definitely the correct approach.
I have a pilot thing working which parses an XML file, displays it in a syntax-highlighted editor, and simultaneously displays its DOM tree in an expanding tree widget and a list of its xml:ids (with the first 30 characters of the element's text) in a list view. This is pretty good, but I know in my heart that what I really need is a model/view architecture whereby the QDomDocument is the model, and these three views are all synchronized so that when you click on (say) an xml:id in the id list, the dom tree opens and highlights the corresponding element, and the editor scrolls to, and highlights, the same element. QT's model/view stuff hurteth my brane mightily, but I managed to understand it for long enough to write the little SVG resource applet, so I'm sure I can do it again.
My previous little demo app came up against a huge roadblock when any reference to a QTreeWidget would cause an application crash with a segmentation fault. As far as I can see, this was not due to anything in my code, so eventually I went back to the drawing board and rewrote the app using the UI designer in QT Creator. This is now working (parsing an XML string into a DOM tree and displaying it in a QTreeWidget). Something in the process of creating the QTreeWidget in the UI class seems to protect against the seg fault.
I'm now working on having the app build an index of xml:id attributes, from a persistent DOM model, and figure out how easy it is to keep the index and the DOM model aligned.
I've set up a simple project for learning about DOM functionality in QT. I haven't really got very far yet, but I have learned that by default, the QXmlDocument object ignores nodes consisting only of whitespace (obviously not written by humanists), and that some kind of workaround is required when parsing an XML stream. That's the only unpromising thing so far...
MJ came by today and we discussed the framework of the application, and made some preliminary decisions. This is my attempt to capture what ended up on the whiteboard.
The text side of the application (the TEI <text>
element, in other words), will probably be handled as a DOM structure. It doesn't make much sense to try to model it in any other way, since it is directly editable (in that any element along with its children can be called up and altered by the user). We will need to keep an updated index of all the links between elements and <zone>
s, though; each link would consist of a source element's @xml:id
, the target zone's @xml:id
, and the category of the link. It appears that categories will be applied, not to <div>
s or <zone>
s as before, but to links. This is necessary because the same <zone>
may be linked from multiple elements for multiple purposes, and the same element may be linked to multiple <zone>
s for multiple purposes.
In the interests of simplicity, I think it will make sense to write out the index of links into the TEI file directly. This can be done in categories, something like this:
<linkGrp type="myCategory"> <link targets="#elementA #zoneB" /> <link targets="#elementX #zoneY" /> </linkGrp>
I'm not sure yet where we should be storing the <linkGrp>
elements; the header is the natural place, but none of its components seems to provide a good home for them.
These sets of elements can be rapidly read by the app when loading an existing file. However, for completeness, when saving, we should also supply the appropriate @facs
and @corresp
attributes on elements linking them to <zone>
s, so that a processor not aware of the linkGrp system can still parse the relationships appropriately (although it would be unaware of the category information).
The index of all these relationships needs to be able to update itself in response to changes in the DOM, and it also needs to provide a brokering role enabling the GUI to display the right <zone>
when an element is selected, or the right element when a <zone>
is selected. In both cases, when it knows that there are multiple possible candidates, it should pop up a selection menu of some kind. It will also need to be able to display a list of every element which is a descendant of <text>
and has an @xml:id
, in the form of its <@xml:id>
followed by some characters of its text content, so users can identify what they want to link to.
On the other side of the application, this is the basic model. (After our discussion, I did some more reading and realized we had been wrong about one Qt relationship, that between QGraphicsView and QGraphicsScene, so this description re-works that relationship.)
- At the top is
HcmcFacsimile
, a container for all the graphical constituents of the model. This maps to a TEI<facsimile>
element. HcmcFacsimile
contains aQVector<HcmcSurface>
, which manages all the surfaces in the project.HcmcSurface
maps to the TEI<surface>
element. It descends fromQGraphicsScene
. It contains oneHcmcPixmapItem
, a descendant ofQGraphicsPixmapItem
; this corresponds to a TEI<graphic>
element.HcmcSurface
also contains aQVector<HcmcZone>
. It needs its own unique@xml:id
.- Each
HcmcZone
corresponds to a TEI<zone>
element, and descends fromQGraphicsPolygonItem
. This is the most challenging class to write, because it needs three states (invisible, view, and edit). It will also need a unique<@xml:id>
, which the indexing broker will have to be aware of. HcmcSurface
will render itself through aQGraphicsView
, tied to a panel in a tabbed panel control.
Just spent an hour trying to get an app to compile using my MdhXmlEditor class. Although I was declaring the header file for the class in the header file for the main window, it couldn't find the class. Eventually I discovered that I had to manually add the header and cpp files for the two libraries I'm using to the QT .pro file, like this:
# ------------------------------------------------- # Project created by QtCreator 2010-03-10T14:41:22 # ------------------------------------------------- QT += xml \ xmlpatterns TARGET = createImtFiles TEMPLATE = app SOURCES += main.cpp \ mainwindow.cpp \ ../imagemarkuptool/trunk/projXmlEditor/mdhxmleditor.cpp \ ../imagemarkuptool/trunk/projXmlEditor/mdhxmlhighlighter.cpp HEADERS += mainwindow.h \ ../imagemarkuptool/trunk/projXmlEditor/mdhxmleditor.h \ ../imagemarkuptool/trunk/projXmlEditor/mdhxmlhighlighter.h FORMS +=
There's no equivalent of Delphi's "Add file to project", but obviously the pre-processor needs to know about this stuff; it can't rely on the compiler to find all the header files.
Did some reading and thinking, to come to this conclusion:
- Each image and its annotation areas should be handled using an MdhSurface class.
- The display of the image and zones should be handled using a QGraphicsScene descendant, which is a member of MdhSurface.
- The image might be rendered as a background using QBrush, or it might be a QGraphicsPixmap item (item 0 in the array held by QGraphicsScene).
- The zones should be handled either by QGraphicsRectItem descendants (if we handle only rectangles), or QGraphicsPolygonItem descendants (if we handle polygons -- see below).
- The actual view should be handled by a QGraphicsView. I'm not sure yet whether that should be part of MdhSurface, or external to it; and I'm not sure at what level we should create an actual widget to be instantiated on a panel. Probably the widget would be MdhSurface, and would have the QGraphicsView as a member, but I'm still fuzzy on that.
I've also figured out a way of encoding polygonal zones in P5. Here's a brief description I sent to the TEI list for some feedback:
The current spec for <facsimile> and its children allows only rectangular zones on a surface:
<surface> <zone ulx="10" uly="10" lrx="50" lry="50"></zone> <zone ulx="60" uly="100" lrx="90" lry="200"></zone> </surface>
specifying two rectangular zones on the surface.
Digging around a bit, I noticed that <zone> can contain <desc>, <desc> can contain <dim>. <dim> "contains any single measurement forming part of a dimensional specification of some sort." So I'm thinking that I can do polygons like this:
<surface> <zone ulx="10" uly="10" lrx="50" lry="50"> <desc> <dim type="point">10,10</dim> <dim type="point">30,30</dim> <dim type="point">50,50</dim> </desc> </zone> </surface>
The standard attributes on <zone> define the bounding rectangle for the polygon (enabling processing by any agent that only handles rectangles), while the points of the polygon (triangle in this case) are defined in the <dim> elements for IMT and any suitably-aware processor to handle.
Created a more informative error message from the QXmlStreamReader error feedback for when well-formedness checking fails. Also replaced the red-cross image used to indicate the line where an error occurs with a capital X drawn in red. This is simpler, and removes the dependency on an image. For cross-platform compiles, the executable ends up in different places, so it's convenient to avoid this kind of dependency until we absolutely need it.
Today I finished the routines that get and set character formats, so the user will be able to customize the syntax highlighting. I've also implemented well-formedness checking using the QXmlStreamReader component, and I'm able to put a little X on the line where the check fails. I have a couple more bits and pieces to do, and after that I'll clean up the code and add more documentation. Then it's on to the image rendering stuff.
Refactored all the xml editor code to make it a standalone component, and renamed the classes with prefix Mdh. Started on the process of allowing write access to the highlighter character formats, so the user could set these. It should work pretty well, but I need to find a way to enforce the use of values from an enum in a method parameter.
Adapted some code from KDE to get line numbering working, in an editor that now descends from QFrame. Before I go any further, I really need to separate out all the editor code from the test project code, and build a proper set of methods for it, so you can set the font and character attributes of the editor and of all the special character formats used for highlighting. Then I can move on to well-formedness checking.
The previous post detailed a bug I thought I'd found when running IMT on Linux under Wine. However, after extensive testing, I've been unable to reproduce it. I think that it was actually caused by the propagation of a file which was created with the previous version, prior to my last couple of fixes, into three more files. Once the <facsimile>
and <text>
elements are out of order, they probably stay that way, but unless a file was corrupted in the first place, I don't think it can happen. So I'll shelve this for the moment, but keep my eye on the markup files created on Linux for any more signs of this.
If you do New / With Same Categories, then choose an image, then save; then you add annotations; you end up with a file in which the <facsimile>
element is wrongly positioned after the <text>
element. I've already added code to ensure that <facsimile>
should always be inserted right after <teiHeader>
, but I haven't checked the code which writes <text>
to make sure that it always places text as the last child of <TEI>
. I need to do that. The problem may only show up on Linux (haven't tested on Windows yet).
It took me a while, but I've finally completed the rewrite of this code. It was worth doing to get a more robust highlighting setup, and it's still very fast, even on large files.
I've been unhappy with the XML syntax highlighter limitations, so I've decided to start again with a more rigorous approach. This is a nice scale of algorithm to get more familiar with C++ syntax too. Made a good start -- processing instructions, doctype declarations and basic open and close tags are already working, and I'm starting in on the attributes.
Found another problem related to the limitations in Wine's implementation of version info routines. Fixed it and released a new version. As I work with the IMT under Wine on the ColDesp project, I may find a couple more of these annoyances. In the long run, this is good, because the app is effectively now supported and working well on Linux.
Did a new release of IMT (1.8.1.6) after I discovered that WINE fails to implement some of the Windows version info routines, meaning that two attributes in the <appInfo>
tag end up empty, and thus invalid. Also did a fix for a bug I'm unable to reproduce, and no-one has reported, but which somehow caused me to end up with a file in which the <text>
element was before the <facsimile>
element -- also resulting in an invalid file.
I have half-way-decent syntax highlighting working now, using a kind of hybrid of regular expression matches, start-end matches, and testing the existing format of text which has already been highlighted. I think this will do for the moment. The only problems arise when attribute+value structures are split across multiple lines. I think we can live with that for the moment.
In the process, I discovered how to convert text documents from UTF-8 using QTextCodec. This is simple enough, but I'll ultimately need to figure out how to detect encodings. I'll do a bit of research on this.
I have all the non-interactive multi-line highlights working (highlights that are discrete, such that if you're inside one, you're not inside the others). I'm just starting on attributes, which are more complicated because they interact with open tags.
Implemented proper regexps for the W3C NameStartChar
and NameChar
components. The only limitation is characters above \xFFFF
, which
won't fit into the \xhhhh
format for QRegExp. Also fixed a couple of
bugs in the existing implementation, by making the attribute matching
minimal (= not greedy), and allowing spaces between attribute names,
equals signs, and attribute values.
We finally have a French interface file for the Image Markup Tool, kindly provided by Marjorie Burghart (EHESS, France). I've built a new version of the application (although no other changes have been made), and added the appropriate credits and acknowledgements in the Help file and on the website.
Made some progress today, getting the XML declaration highlighted, and also simple XML tags. For XML tags, the simplest approach seems to be to treat the tag start (< + optional slash + tag name) as one match, and the tag closer (optional slash + >) as another; this allows for highlighting of the tag contents separately, and also handles the problem of multi-line opening tags (pattern matching in the syntax highlighter component is through QRegExp, which seems to work line by line).
I still haven't figured out how to get the DTD declaration to highlight (it's on two lines); it looks as though I might have to take the same approach as with comments, and I'll need to figure out how to handle two different highlighting requirements using that approach. Handling attributes and their values is going to be a bit more complicated, because I'm not sure that I can definitively say whether an att+value combination is inside a tag or not; QRegExp supports lookaheads (but not lookbehinds). Finally, one remaining problem is specifying with accuracy the XmlName pattern for tags, which I haven't bothered to do yet (I'm just using an ascii model for the moment).
I've found no good existing regexp-based code for syntax-highlighting XML (although this looks helpful), so I'm starting in on my own, by adapting the QT example app which highlights C++. I've changed library and class names, and made one change to highlight XML comments.
MB has created a package and instructions for creating self-correcting paleography exercises for students using the IMT. I've posted her materials and instructions on the site, so others can use them. The instructions are more complicated than they need to be because of a deficiency in IMT 1.x: the fact that you can't easily specify a different location for web view template files. That will be fixed in IMT 2.
I think the next phase of the project should be to write an editor widget that does XML syntax highlighting. QT has the QSyntaxHighlighter class which can do this, based on regular expressions matching each of the items that needs to be highlighted. I've spent a good while looking for an existing implementation of regular expressions to match XML elements, attributes etc., and the nearest thing I've found is this. It's enough to get us started, but I think we'll need more.
There's a good example using C++ in the QT examples folder, and the idea looks fairly straightforward, but one problem is going to be the greediness of matching, which may result in matches for opening tags which include all the way to the final closing tag. We should also think about whether we want to check the correctness of e.g. element names; W3C XML Schema Regular Expressions has support for this, and QTSyntaxHighlighter can apparently have its patternSyntax
set to W3CXmlSchema11
, but the effect of this is not really well documented. We'll have to proceed by trial and error.
When testing at home on a Windows 7 VM, noticed that the latest download is now QT 4.6, so I've installed that locally too. Note that you still have to manually switch the QT version in the preferences screen, otherwise your apps will presumably be compiled and running against the old version. Also found a problem with the install; in order to use QOpenGL, you need to install libglui-dev, but this won't install on my Jaunty machine because it depends on freeglut-dev, which is not available. I'm not worrying about this right now, because I'm likely to move to Lucid Lynx by May. The app compiled and ran just fine, and I found and fixed another little bug.
Added a bunch more features to the pilot application, including saving/reloading state (very simple), sanity checks in case the icon directory is not where it's expected to be, the ability to change the icon directory, an About box, and some tweaks to make for more robust cross-platform compiles. Yesterday I test-compiled on Windows 7 and everything worked.
This phase of the project can now be said to be complete. I've confirmed that all the features we were interested in are present and practical in QT, and got a feel for how to develop a basic GUI application. Progress is much faster than I expected.
Had a very heartening day today: these are the things I learned how to do:
- Create directories, copy files, etc. using platform-independent calls.
- Read an XML file using the
QXmlStreamReader
object (which is rather like a sophisticated SAX parser, or perhaps a nodeIterator). - Create a DOM document and save it to disk.
XML handling looks to be really easy and practical, and my little app is now able to load and save files correctly. All that's left for the trial app phase now is to figure out how to save and restore window geometry and state, and handle enabling/disabling actions.
Moved a lot further forward towards a normal working application today. These are some of the things I learned:
- Although my data is in a QAbstractTableModel descendant, and my first instinct was to give that class a
modified
property to track whether the data was dirty or not, it turns out that for an SDI app like this one, you might as well store that info in thewindowModified
property of theQMainWindow
. This makes things simpler. - When the model's data is changed, it emits a dataChanged() signal, which I was trying for a while to hook up to a custom slot in my MainWindow. I couldn't figure out why this wasn't working. It turned out that:
- the signal and slot must have exactly the same parameter types;
- when you declare, you use variable names, but when you connect the signal and slot, you must leave out the variable names, and only include the ampersand;
- omitting the const keyword will cause the connection to fail.
- QDebug is really neat and easy to use. Just include
<QDebug>
in the header, and callqDebug()<<"My message"
to write it out to the Application Output console,
Learned a whole lot of stuff about the model/view architecture today, and finally managed to get items to be user-checkable in such a way that the user's actions are communicated back to the model. The next stage here, I think, is to clean up the code and comment the heck out of it so it can serve as a model for future similar scenarios.
Working on the pilot application using the SVG icon set I've amassed, I've made some significant progress today. I've learned how to use a QAbstractTableModel descendant to model the list of icons along with their checked states, and also to display the model through a QTreeView. The next phase is to figure out how to make their checked state editable (right now, checkboxes are displaying but not responding to clicks).
Meg and I created a SourceForge project for the Image Markup Tool, which is here. After adding Meg as a developer and giving her admin privileges, we set about figuring out SVN. It took a while to get the hang of it, so I'm documenting what I ended up doing to get it working.
Initially, you get an empty repository which is at "revision 0". I checked that out using svn co
into a directory on my HD. That process gives you a .svn
directory which has configuration and revisioning data in it. The structure is imagemarkuptool/.svn
. Then we created a directory structure based on /trunk/, containing a subdirectory for each of our initial sub-projects (which will eventually be combined into the final application). I put that directory, including the code for the initial test project with SVG icons, into the /imagemarkuptool/
directory, alongside the .svn
directory. Then I ran svn add trunk
to add that directory and its tree to the project; then svn commit
to upload changes to SVN. Meg checked out the tree and confirmed the structure is as planned.
Submitters requested some detail to go along with the budget, so after some back-and-forth with MT and others, wrote some text.
Worked with MT to list modular components and the amount of programming time we estimate they'll take, both for the IMT and for the publication engine, and turned this list into a budget to go to DI.
Met with MT and discussed DI's wishlist for the EMiC markup tool and publication platform. Following this, I sent a long email to both explaining my take on the situation (that the markup tool and the publication engine are separate projects and ought not really to be connected, and that I'd be less likely to be much involved in a publication engine). The feature list is shaking down. One key aspect that jumps out is the requirement for polygonal zones, which precludes the use of the standard FACS module in TEI; we may need to lobby for polygonal areas, and/or go back to SVG.
Good meeting to look at collaboration on a QT version of MT as part of the EMIC project. We may be able to co-write it as a cross-platform app, targeted initially at EMIC, but released as a general-purpose tool.
While on vacation I got the new QT 4.5 IDE working on my home computer, and it's very impressive. I think it's possible that it would be a good choice for development of IMT 2.0; it has:
- good graphics and drawing support, including SVG
- good XML support
- good Unicode support
- native compilation for Windows, OSX and Linux, as well as embedded Linux, S60 and Windows CE
- a new LGPL licence, which frees it up a bit
Drawbacks would be the need to learn C++ and QT (no small task), but that would be a good skillset to acquire for many future projects, I think.
Tatjana Radzeviciene contributed Lithuanian and Russian interfaces, which I've built into the program; updated the Help and website credits, and released a new build. I did notice that some of the captions had ended up with return characters at the end of them, so I had to remove those from the interface files; I think this should be something the Translation screen could prevent, so I'll have a look at that.
The file structure associated with installing IMT as a PortableApp includes a file called appinfo.ini, which PC pointed out hasn't been updated for ages, and still shows version 1.7. Updated it, rebuilt the installer, and posted it to the site.
I think the PortableApp stuff is probably superfluous; version 2 will just have a simple installer.
PC sent a new translation file and reminded me I hadn't changed the date and version on the help file, so I built a new installer, and also took the opportunity to prune some of the unneeded old copies of source files that were being bundled into the app.
HB reported that while images automatically resize themselves to the image component area when you load a file, that doesn't happen when you load an image into an existing or new file. I've now abstracted the resize component into a separate function, and added a call to it when you load an image; I also tweaked the resizing code to make it more precise about the resizing (it wasn't taking account of the size of scrollbars on the image display component).