subcommanderblog.wordpress.com

Subcommander Development Blog

Posts Tagged ‘Qt4

Subcommander Status

with 3 comments

Hi,

just wanted to let you know that I’m still working on Subcommander :-)

Subcommander

I’m still rewriting the working copy (custom implementation of QAbstractItemModel) code using test driven development. A couple of days ago I modified the gui to use the new code for the first time. And it worked nearly out of the box :-) Because of the test coverage caused by using tdd it will be a lot easier to maintain and extend in the future.

The next step is to add the working copy filtering code to my own custom QAbstractProxyModel. The Beta 5 code is based on QSortFilterProxyModel and I was unable to get it to do what I want. The way QSortFilterProxyModel handles the QModelIndex stuff  simply does not fit with my code. Debugging it was just crazy.

When that’s finished (will be beta 6) I will be able to work more on features and visible improvements instead of internal design stuff.

Hudson

What else? I have set up a (private) Hudson build of the Subcommander /trunk. It is running subcommanders google mock based test suite, checking coverage with gcovr (I had to tweak it a little bit) and is running a code analysis using cppcheck. Cool ;-)

I’m now looking at cukebins to see if it is possible to create cucumber based acceptance tests for subcommander.

Subversion mingw port

I’m also working on a port of subversion to mingw. My final goal is to build Subcommander on Windows with mingw. I have a patch to build subversion on mingw but there are still some failing tests.

Since I didn’t work on it for a couple of month I’m now trying to upgrade the patch to subversion trunk….

Progress is slow but steady…  maybe I’m trying to do too much ;-)

Advertisements

Written by hauner

Sunday, 20 June, 2010 at 17:34

Subcommander Status..

with 6 comments

Hi,

It looks like I’m currently (currently!?) losing a bit track of things in Subcommander development… :-(

I tried to upgrade from Qt 4.4 to Qt 4.5. Bam, a couple of (my few) tests for the custom QAbstractItemModel (which is the base for the working copy display) don’t run anymore. The working copy view now crashes randomly. Great. Unfortunately I have so few test for the model, that I’m sure I will break other things trying to fix the broken tests. I’ve already wasted a lot of time debugging that code in the past with all the QModelIndex objects you can’t look into. I don’t want to waste more time on this….

So I took another step back (moving the proxy model into the TODO list again) and started another round on the QAbstractItemModel stuff. I’m now trying to TDD the model into existence. This is going a bit slow because I ‘m still a beginner at TDD. But I have about 20 tests around the new custom model implementation now and slowly it begins to do something.. :)

So far I always used cppunit to write tests. I think I’m going to drop it in favor of gtest/gmock. Allthough gmock does work together with cppunit, it is simpler and faster for me to create new tests with gtest instead of cppunit. With cppunit I always had a header and a implementation file. With gtest I just need an implementation file. This is a lot easier. Maybe this would work with cppunit too, but since gmock includes gtest anyway I have one dependency less to care for (just gmock instead of cppunit & gmock).

Another big issue that keeps me from making real progress is the maintainability of 3 build environments. Another time waster. I’m considering to move to qmake as the build environment and to drop the hand made configure/XCode/Visual Studio builds.

I also wonder if it wouldn’t be a lot easier to drop Visual Studio completely and use MinGW to build Subcommander on Windows. That way I would more or less use the same build environment (gcc and friends) on all the three platforms (Mac/Linux/Windows). This should improve maintainability as well.

So I’m still working on Subcommander, but I can’t tell when there will be a new release… but maybe I’m the only Subcommander  user anyway… ;)

Written by hauner

Sunday, 2 August, 2009 at 14:52

Posted in subcommander

Tagged with ,

Building Qt on Windows…

leave a comment »

Building Qt (4.5.1) on Windows in a Fusion virtual machine with limited disk space is an adventure… :)  The build eats endless Gigabytes. Unfortunately the configure.exe is a bit limited and doesn’t allow to disable building of the demos and the examples.

After getting some hints and ideas by googling I did the following steps:

  1. I did run configure.exe with the -fast option. According to configure.exe -help, -fast only creates Makefiles for library and subdirectory targets.
  2. Then I entered the src directory (that is the source directory for the libraries) and called nmake. That way qt builds only the libraries without the demos or examples.
  3. After  the library build finished, I called nmake clean in the src directory. make clean removes all temporary stuff in the src directory (like the obj files) but NOT the finished libraries and binaries from the top level bin and lib directory.

If you don’t plan to rebuild the libraries the third step frees up about 5 Gigabytes(!) of disk space. I wouldn’t recommend cleaning the src directory if rebuilding is required though. Building the libraries takes several hours…

Written by hauner

Wednesday, 27 May, 2009 at 20:09

Posted in subcommander

Tagged with

creating a custom QAbstractProxyModel

leave a comment »

Part I, getting started.

I’m currently implementing a custom QAbstractProxyModel for subcommanders working copy view. The first reason is that I want to improve the status filtering in subcommander which is not possible with the current code based on QSortFilterProxyModel. The second reason is to get full control of sorting. My attempt to add sorting to the current code failed in such a frustrating way that I don’t want to track down the problem and simply start again with fresh code that I will be able to debug :-)

Before starting with the interesting stuff (filtering and sorting) I will create a simple proxy model which does not do any sorting or filtering. It will just pass through the original (tree) model without modification. Getting this running is a big step toward the final goal.

Important for a proxy model is that is emits all the signals the source or original model emits. To achieve this the proxy model must connect to all (interesting) signals from the source model and emit the same signals with proper information from the proxy model.

I have created a test checking two signals emitted from the proxy model. To count the emitted signals I’m using a SignalTarget class which connects to the proxy signals and checks if the signals were emitted. Since the verification (CPPUNIT_ASSERT) is done inside this class it is a mock. It replaces the widget (in my case a QTreeView) that will be connected to the proxy model in the production code.

class  SignalTarget : public  QObject
{
  Q_OBJECT; 

public:
  SignalTarget( QObject* src );
  ~SignalTarget(); 

  virtual void verify(); 

public  slots:
  void modelAboutToBeReset();
  void modelReset(); 

private:
  QObject* _src; 

protected:
  int  _hitModelAboutToBeReset;
  int  _hitModelReset;
};
 

The idea is to count the emitted signals and check the results in the verify method. It is not yet complete. It does handle only a subset of the signals we need to check. I have declared verify as virtual so it easy to re-implement and do the actual check. The default implementation is empty. The constructor and destructor will simply connect and disconnect all signals the we need to handle in the proxy model. The slot method names correspond to the model signals.

A test method can now use SignalTarget to check the signals from the proxy model. You can see the complete test below. _proxy is the proxy model and gets created by the test setup. The reset() method triggers both signals. Because I care only for the reset signals I have overwritten verify() to simply check their call counts.

void WcViewItemProxyModelTest::emitsModelResetSignals()
{
  class Target : public SignalTarget {
  public:
    Target( QObject* src ) : SignalTarget(src) {
    }

    void verify() {
      CPPUNIT_ASSERT_EQUAL( 1, _hitModelAboutToBeReset );
      CPPUNIT_ASSERT_EQUAL( 1, _hitModelReset );
    }
  };

  Target target(_proxy);
  _proxy->reset();
  target.verify();
}
 

This is similar to the signal testing I mentioned in my last article. The only difference is that originally I also tried to check QModelIndex indices which didn’t work to well. The problem was creating valid expected indices to assert on. I tried to remember indices from previous signals and build new expected indices (sibling, parent,child) from them. Unfortunately this approach got complicated rather fast so you couldn’t follow the code anymore.

To get the test running I simply connect the soure model modelReset signal to the srcModelReset() slot in the proxy model and call reset(). reset() will emit both signals the test wants to see.

void WcViewItemProxyModel::srcModelReset()
{
  reset();
}
 

Very simple so far, but one has to start somewhere. :-) I guess it will get more interesting for some of the other signals the proxy model has to handle. More on this in the next article.

Written by hauner

Sunday, 8 February, 2009 at 19:07

Posted in subcommander

Tagged with , ,

QAbstractItemModel again..

leave a comment »

I have implemented subcommanders working copy view as a custom QAbstractItemModel (since subcommander displays a directory tree it is a tree model). On top of the model is a proxy model based on QSortFilterProxyModel to filter working copy items based on their subversion status.

I have a mixed feeling about Qt4’s item models. It has some benefits but also a few problems.

On the positive side is the view and model separation, which of course is good. The Qt item model is really a view model and not simply a data model. But that’s not a problem, you just have to accept that there a few view things you have to handle in the model.

Another useful feature is the proxy model. As already said I use it to filter items by their working copy status and I also use it to realize part of the flat and the tree view display. Switching between both is a matter of toggling an option in the proxy model and tweaking a few options in the widget that is connected to my model.

The bad thing about the item model stuff it that it is really complicated to get it working properly. Apart from all the documentation that Qt provides on implementing custom models, it is still difficult to get all the QModelIndex stuff running.

Another issue is that it is really hard to debug. You may have a small bug in your model that crashes the application. Now debug that. :-( The issue is that you can’t directly watch the data behind a QModelIndex. So it is unfortunately very frustrating to find out what’s wrong.

From my point of view, item models are too complicated. Said that, it is what you have to use in Qt4 if the simpler model stuff doesn’t work for you. And it doesn’t work for me.

So what can I do?

A while ago I was trying to find a bug in my custom QAbstractItemModel. I couldn’t track it down with the debugger and finally started to write a couple of unit test for it using cppunit. (Yes, Qt has a unit test framework that supports signals and slot, but I’m already using ccpunit and I don’t want to have tests in different frameworks :-)

I didn’t find this bug with the tests but it helped me with another signal issue in the custom model. Under certain data constellations a change in the underlying item model wasn’t properly notified to the proxy model.

I have tried two approaches. The first checks for emitted signals from the model when inserting data into the model. The second checks the existence of index values which should exist after inserting data into the model.

I think I prefer the second approach, but I’m not yet satisfied with the test code itself. It is large and also a bit messed up…

I will describe both ways in the next articles and also write about my experience with creating a custom QAbstractProxyModel.

Written by hauner

Sunday, 1 February, 2009 at 21:36

Posted in subcommander

Tagged with ,

submerge

leave a comment »

I always liked the way the FileMerge utility on MacOSX connects a left and right file difference with a curved connection. I played with this a couple of years ago but since Qt3 did not support antialiasing out of the box I dropped it again. Without antialiasing a curved line does not look very good.

After spending some time improving the redrawing issues in submerge I mentioned in Qt4 (unfortunately there are still a few pixel glitches, but it is a lot better than before) I implemented a curved connection with antialiasing. A reward for fixing most of the redrawing issues ;-)

Since Qt4 supports antialiasing simply by setting a render hint on the QPainter class it was quite easy to implement this time. Most work was to get access to proper size information for a single difference block. That is how many lines in the left file (OriginalLength) and how many lines in the right file (ModifiedLength) create a difference block and have to be connected. Luckily this information was already calculated by the diff code and I only had to add it to the existing BlockInfo info class.

The drawing code then goes like this:

...

Line lline = _left->getLine(curLine);
Line rline = _right->getLine(curLine);

const BlockInfo& lb = _model->getInfo( lline.getBlockNr() ).getBlockInfo();

...

svn::Offset sl = lb.getStart() + lb.getOriginalLength();
svn::Offset sr = lb.getStart() + lb.getModifiedLength();

...

int top = tpc.getLineY((int)lb.getStart());
int botLeft  = tpc.getLineY((int)sl);
int botRight = tpc.getLineY((int)sr);

QPainterPath path;
path.moveTo(0,top);
path.lineTo(0,botLeft);
path.cubicTo(lnDefWidth-5,botLeft, 5,botRight, lnDefWidth,botRight);
path.lineTo(lnDefWidth,top);
path.lineTo(0,top);

DiffInfo& info = _model->getInfo(block);
QColor lcolor = getBgColor( lline.getType(), info.getMergeType(), true );
QColor rcolor = getBgColor( rline.getType(), info.getMergeType(), false );

QLinearGradient gradient(0,0,lnDefWidth,0);
gradient.setColorAt(0, lcolor );
gradient.setColorAt(1, rcolor );
QBrush brush(gradient);

pp.setRenderHint(QPainter::Antialiasing);
pp.setPen(Qt::NoPen);
pp.setBrush(brush);
pp.drawPath(path);

...

Above you can see the most interesting parts of the code. I have omitted the code that takes care of painting the connection only once for every block.

If the current line is part of a difference block I calculate the bottom y pixel position for the left block part based on the OriginalLength and the right block part based on ModifiedLength.

Then I create a cubic PainterPath that describes the curved connection. lnDefWidth is a constant value the represents the width of the glue area between the left and right file.

Next I create a gradient between the background colors of the left and the right file to get a smooth transition if the colors are different (on merging a selected left or right block gets a different color to give feedback which file was choosen for the merged file).

Finally the connection gets drawn with antialiasing to get a smooth curve.

Below is a submerge screenshot showing what it looks like. The two files are unrelated to get a lot of differences :)

submerge with a curved connection between left and right file

MacOSX: submerge with a curved connection between left and right file

Written by hauner

Sunday, 22 June, 2008 at 11:25

Posted in submerge

Tagged with

Qt4 Item View Models..

leave a comment »

Today I learned something about implementing your own QAbstractItemModel with Qt4.

I worked on the subcommander (2.0) bookmark view to hide incomplete bookmarks (as in 1.2). If a bookmark has no source url, it is hidden. That’s useful if you don’t need the trunk/branches/tag repository bookmarks. Hiding them also removes some noise the bookmark view.

The second reason is that you can’t trigger a command on an incomplete bookmark from the gui. This avoids checks at various places if a bookmark is valid. The code is a lot cleaner if it can rely on a properly filled bookmark object.

The bookmark view is already based on a custom item model, so the easiest was to wrap the existing item model in a filter proxy model (QSortFilterProxyModel) and overwrite filterAcceptsRow().

Implementing the proxy model was just a few lines. No problem. Replacing the old model with the proxy model was a bit more work because the bookmark view class directly calls custom methods on the item model (to get a bookmark for a given index and getting the model index based on a bookmark object) that are not available in Qt’s interface.

That didn’t look right. I would have to change all this places to use the proxy model and have to delegate my custom methods through it? Ideally, it should be transparent. Just wrap the model into the proxy model, set it on the view, done.

I solved the “get bookmark for id” issue by making 2 changes. First I introduced a custom “role” to the item model. I can now call the data() method on the model to get the bookmark object. No need for a custom method. The second change is, that I call the data() method on the model index instead of the item model. I didn’t know until now that QModelIndex has its own data() method that delegates to the model. That way I don’t even need to know the model object.

old code:

void BookmarkView::contextMenuEvent( QContextMenuEvent* e )
{
  Bookmark* bookmark = _itemModel->bookmark( indexAt(e->pos()) );
  if( ! bookmark )
    return;
  ...
}

new code:

(since i need the interesting part multiple times I moved it to an extra method)

Bookmark* BookmarkView::getBookmark( const QModelIndex& index )
{
  return index.data(BookmarkViewItemModel::BookmarkRole).value<Bookmark*>();
}

void BookmarkView::contextMenuEvent( QContextMenuEvent* e )
{
  Bookmark* bookmark = getBookmark(indexAt(e->pos()));
  if( ! bookmark )
    return;
  ...
}            

I don’t have an idea for to the reverse lookup (bookmark to index) so I added a delegating method to the proxy model until I have a better idea.

So, what I have learned is:

  • avoid custom methods in an item model.
  • use custom roles to get data from the model (looks obvious now..;).
  • call data() on the index instead of the model.

Written by hauner

Sunday, 20 April, 2008 at 23:33

Posted in subcommander

Tagged with