Author Archives: Andy Brice

WTF Google Ads?

Google Ads has emailed me to tell me that one of my ads has been disapproved for ‘shocking content’.

Yep, an ad for seating planning software that has been running continuously since 2011. You can see the full text of the ad above. I am at a loss to know what is shocking about it.

The following are covered under Google’s shocking content policy:

  • Promotions containing violent language, gruesome or disgusting imagery, or graphic images or accounts of physical trauma
  • Promotions containing gratuitous portrayals of bodily fluids or waste
  • Promotions containing obscene or profane language
  • Promotions that are likely to shock or scare

Er, no idea how any of those apply to my software. I don’t even see how it can even be falling foul of the Scunthorpe problem. Perhaps they are shocked how cheap it is?

This is far from my first brush with this sort of thing from Google Ads. Back in 2015 I was told that hyperlinking from my domain to any another domain was a breach of Adwords policy. Since 2005 I have paid Google tens of thousands of pounds to run ads on their system. In return they have wasted loads of my time with endless changes to the platform and arbitary and erroneous enforcement of their own policies. They feel less like a partner and more like an enemy.

I’m not too bothered about one ad being dissaproved. Especially during COVID, where almost no face-to-face events are happening anywhere in the world. But experience shows they will probably start disapproving many more over the next few weeks. I have clicked the link to appeal the disapproval. Hopefully, sometime in the next few weeks some under-paid, under-trained contractor, who might not speak great English, will re-approve the ad. But maybe not. Who can say in this opaque, Kafkaeaque, ‘computer says no’, outsourced world that we have collectively built for ourselves.

** Update 04-May-21 **

I appealed the dissaproval and the appeal failed. No reason given for why it is shocking. A second ad has been dissaproved for ‘shocking content’. I suspect many more will follow.

How to add a dark theme to your Qt application

Dark themes are now available for Windows 10 and Mac and it is increasingly expected that desktop applications will offer a dark theme. Previously Qt support for dark themes was patchy. But I am happy to say that it now seems to work fine with Qt 5.12.2, and I have added dark themes to both Windows and Mac versions of my Easy Data Transform and Hyper Plan applications.

Easy Data Transform for Mac with a dark theme:

Easy Data Transform for Windows with a dark theme:

Hyper Plan for Mac with a dark theme:

Hyper Plan for Windows with a dark theme:

I haven’t decided yet whether to add a dark theme to PerfectTablePlan.

Adding dark themes was a fair amount of work. But a lot of that was scouring forums to work out how to integrate with macOS and Windows. Hopefully this article will mean you don’t have to duplicate that work.

Dark themes work a bit differently on Windows and Mac. On Windows changing the UI theme to dark won’t directly affect your Qt application. But you can use an application stylesheet to set the appearance. On Mac changing the UI theme to dark will automatically change your application palette, unless you explicitly block this in your Info.plist file (see below). On both platforms you will need to change any icons you have set to the appropriate light/dark version when the theme changes. Some of this may change in future as dark themes are more closely integrated into Qt on Windows and Mac.


You can add the following helper functions to a .mm (Objective-C) file:

#include "Mac.h"
#import <Cocoa/Cocoa.h>

bool macDarkThemeAvailable()
    if (__builtin_available(macOS 10.14, *))
        return true;
        return false;

bool macIsInDarkTheme()
    if (__builtin_available(macOS 10.14, *))
        auto appearance = [NSApp.effectiveAppearance bestMatchFromAppearancesWithNames:
                @[ NSAppearanceNameAqua, NSAppearanceNameDarkAqua ]];
        return [appearance isEqualToString:NSAppearanceNameDarkAqua];
    return false;

void macSetToDarkTheme()
   if (__builtin_available(macOS 10.14, *))
        [NSApp setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]];

void macSetToLightTheme()
    if (__builtin_available(macOS 10.14, *))
        [NSApp setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameAqua]];

void macSetToAutoTheme()
    if (__builtin_available(macOS 10.14, *))
        [NSApp setAppearance:nil];

The macSetToLightTheme() and macSetToDarkTheme() are useful if you want to give the user the option to ignore the OS theme. Call macSetToAutoTheme() to set it back to the default.

Corresponding header file:

#ifndef MAC_H
#define MAC_H

bool macDarkThemeAvailable();
bool macIsInDarkTheme();
void macSetToDarkTheme();
void macSetToLightTheme();
void macSetToAutoTheme();

#endif // MAC_H

You then need to add these files into your .pro file:

macx {
   HEADERS += Mac.h

You can detect a change of theme by overriding changeEvent():

void MainWindow::changeEvent( QEvent* e )
#ifdef Q_OS_MACX
    if ( e->type() == QEvent::PaletteChange )
        // update icons to appropriate theme
    QMainWindow::changeEvent( e );

If you decide you *don’t* want to add a dark theme to your Mac app, the you should add the bold entry below to your Info.plist file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "">
<plist version="1.0">

This will then force it to be shown in a light theme, regardless of the theme the operating system is in.


To set a dark theme palette you can use a stylesheet:

QFile f( ":qdarkstyle/style.qss" );
if ( !f.exists() )
   qWarning() << "Unable to set dark stylesheet, file not found";
{ QFile::ReadOnly | QFile::Text );
   QTextStream ts( &f );
   getApp()->setStyleSheet( ts.readAll() );

The stylesheet I used was a modified version of qdarkstyle from a few years ago.

To unset the stylesheet and return to a light theme just call:

getApp()->setStyleSheet( "" );

Alternatively you can do it by changing the application palette.

Windows helper functions:

bool windowsDarkThemeAvailable()
    // dark mode supported Windows 10 1809 10.0.17763 onward
    if ( QOperatingSystemVersion::current().majorVersion() == 10 )
        return QOperatingSystemVersion::current().microVersion() >= 17763;
    else if ( QOperatingSystemVersion::current().majorVersion() > 10 )
        return true;
        return false;

bool windowsIsInDarkTheme()
    QSettings settings( "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", QSettings::NativeFormat );
    return settings.value( "AppsUseLightTheme", 1 ).toInt() == 0;

Currently there seems to be no way to conect to a signal or event that shows the theme has changed in Windows. So I connected to a signal from a QTimer that fires every 5 seconds to check windowsIsInDarkTheme().


When the theme changes you potentially need to update any icons you have set, e.g. for the toolbar.

In a light theme you can usually set the active icons and let Qt calculate the corresponding disabled icons. This doesn’t work for a dark theme as you want the disabled icons to be darker than the enabled icons, rather than lighter. So you can either calculate the disabled icons programmatically or you can provide a set of disabled icons as well. I opted for the former.

Assuming your icons are set up as resources under :/icons/dark and :/icons/light you can do something like this:

QString getResourceName( const QString& iconName, bool dark )
    return QString( ":/icons/%1/%2" ).arg( dark ? "dark" : "light" ).arg( iconName );

QPixmap getPixmapResource( const QString& iconName, bool dark )
    QString resourceName = getResourceName( iconName, dark );
    QPixmap pixmap = QPixmap( resourceName );
    Q_ASSERT( !pixmap.isNull() );
    return pixmap;

QIcon getIconResource( const QString& iconName, bool dark )
    QIcon icon;
    QPixmap pixmap = getPixmapResource( iconName, dark );
    icon.addPixmap( pixmap );
    if ( dark )
        // automatic disabled icon is no good for dark
        // paint transparent black to get disabled look
        QPainter p( &pixmap );
        p.fillRect( pixmap.rect(), QColor( 48, 47, 47, 128 ) );
        icon.addPixmap( pixmap, QIcon::Disabled );
    return icon;

Then you can reset the icon for the appropriate theme with:

bool isDark()
#ifdef Q_OS_MACX
   return macIsInDarkTheme();
   return windowsIsInDarkTheme();
myButton->setIcon( getIconResource( "my_icon.png", isDark() ) );

You may also be able to update icons through QIcon::setThemeName(). But I didn’t explore this in any detail.

Note that you probably don’t want the enabled icons to be pure white, as it is a bit too visually jarring against a dark theme.

Creating a forum for your product

I started selling software online 16 years ago. Until this year I never had a forum for any of my products. I handled customer support for PerfectTablePlan and Hyper Plan by email and kept customers up-to-date with an opt-in email newsletter. But I rethought this position with my latest product, Easy Data Transform and started a forum at in December 2020.

My ISP offered various forum software packages, but I really wanted Discourse, as I consider it head and shoulders above all the other forum software I have interacted with as a user (even if I find the badge system a bit patronising). I didn’t want the hassle of setting up and patching a Discourse server, so created the forum through (previously It was suprisingly easy to set-up. And it gives the option to export everything, in case I want to part ways with them. The sheer number of options in Discourse are quite daunting, but I stuck with the defaults for the most part.

Some people use Facebook Groups for their product forums. Ugh. You have almost no control of such a forum. Facebook could even be showing ads for your competitors on your forums. Or they could just decide to shut you down and delete all the content. That is before we get on to the fact that Facebook make their money monetising hatred and abusing our privacy at an industrial scale. No thanks.

The advantages of a forum are:

  • Letting customers talk to each other, and post content helps to create a community around the product. Which, in turn, can add a lot of value to your product.
  • Customers can help each other with support questions. Sometimes they will answer before you are able to or will give a different perspective. Or even give a better answer.
  • If a customer asks a question that has already been asked, you can send them a link to the appropriate forum page.
  • It is a quick and easy channel to communicate with customers. I can post a link to a new snapshot release in a few minutes. This is much quicker than sending out an email newsletter. It is also more interactive as customers can respond on the forum and see each other’s responses.
  • A lively forum is ‘social proof’ that your product is worth buying.
  • A forum with lots of content should have a large SEO footprint.

The disadvantages of a forum are:

  • The time to maintain it. A forum that is broken or full of spam and unanswered questions is worse than no forum.
  • Disgruntled customers potentially airing their grievances in public.
  • The cost of the forum.
  • An empty forum looks bad.
  • Bad actors can be a pain. For example, people posting links to spam or competing products.

It probably only takes me 1-2 hours per week to post on the forum at present. Some of that is time I would have spent answering support emails. If that rises substantially then I may have to delegate it.

I try very hard to provide a good product, with good support and haven’t had any issues with negativity, so far. But I know from my experiences moderating Joel Spolsky’s Business of Software forum that moderating a busy forum can be tricky, time-consuming and emotionally draining.

The cost of the forum is currently around $20 per month, so pretty low. That may climb, but hopefully sales will be climbing as well.

I was a bit worried about whether the forum was going to look empty. I warned customers that the forum was an experiment and would be closed if there wasn’t enough activity, to manage their expectations. I also created a ‘sock puppet’ account and ‘seeded’ the forum with a few support questions that I had been previously asked by email (with the permission of those that asked) and then posted answers. But I only did this a handful of times and then the forum started to take off.

I have heard stories of people getting 1000+ spam posts a day on their forum. But I haven’t had any issues with bad actors, so far. I’m not sure how much of that is down to Discourse and how much of it is down to luck. But, no doubt issues will occur at some point.

I still have my product newsletter, which I send out every few weeks when there is a new production release.

Overall I am pretty happy with how the forum is going. Should you have a forum for your product? As always, it depends. I think you should consider it if:

  • Your customer base isn’t tiny.
  • You want to interact with your customers and get feedback. This might be less the case with mature products.
  • You have the time and energy to police and maintain it.
  • Your product is relatively open ended or complex. For example, if your product just checks whether website are up or down, there is probably a very limited amount you can discuss.

Adding colour schemes to Easy Data Transform

Easy Data Transform user interface.

The colours used in Easy Data Transform make no difference to the output. But the colours are an important part of a user interface, especially when you using a tool for significant amounts of time. First impressions of the user interface are also important from a commercial point of view.

But colour is a very personal thing. Some people are colour-blind. Some people prefer light palettes and others dark palettes. Some people like lots of contrast and other don’t. So I am going to allow the user to fully customize the Center pane colours in Easy Data Transform.

I also want to include some standard colour schemes, to get people started. Looking around at other software it seems that the ‘modern’ trend is for pastel colours, invisible borders and subtle shadows. This looks lovely, but it is a bit low contrast for my tired old eyes. So I have tried to create a range of designs in that hope that everyone will like at least one. Below are the standard schemes I have come up with so far. They all stick with the convention pink=input, blue=transform, green=output.

Which is your favourite (click the images to enlarge).

Is there a tool that you use day to day that has particular nice colour scheme?

I hope to also add an optional dark theme for the rest of the UI in due course (Qt allowing).

Stalking website visitors with Microsoft Clarity

Microsoft Clarity is a new service that allows you to see, in detail, how visitors are interacting with your website. It includes:

  • heat maps, showing where visitors are clicking or touching or how far they are scrolling
  • recordings of visitor sessions, including mouse movement, clicks, touches and scrolling

You just need to get a Javascript snippet from (for which you need a Microsoft login) and paste it into the header of each page. You can then login to at a later time to see your results. The service is free.

I tried it on my website. Here you can see a click heatmap for the buy page:

People are clicking all over non-hyperlinked text. Hmm. Perhaps they somehow couldn’t the see the effing enormous blue button next to it? Notice that numbers are starred out to avoid personal information, such as credit card numbers.

You can also see how far visitors scroll down the page with scroll heatmaps:

So I can see that the buy button is appearing well above the fold.

You can also watch recodings of people interacting with the website, showing their mouse movements, clicks, touches and scrolling. This is where things start to feel a bit stalkerish. You don’t get any identifiable information on the visitor beyond their country, browser and their operating system and I’m ok with people watching me interact with their websites like this. But it still feels a bit voyeuristic. The results are also a bit strange. Some people just click all over the place and highlight random text (touches are tracked separately from clicks). There is a distinct danger that you could watch hours of sessions and come away without much actionable information.

You can filter the information in various ways, including by country or referring website. You can even filter to see sessions with ‘Rage clicks’ (where the user has clicked or tapped repeatedly in the same area).

Watching a few sessions with ‘rage clicks’ I could see that some people indeed seem completely unable to see the effing enormous blue ‘buy now’ button on the buy page. So I have also added a text hyperlink where most people are clicking in the text and will probably try changing the button colour. Perhaps to shocking pink!

Running Pingdom Website Speed Test on the Easy Data Transform home page, both with and without Clarity a few minutes apart, I can see that it had some effect on speed, but not too much.

Without Clarity script: 175.2kb of scripts, 7 script requests.

With Clarity script: 194.3kb of scripts, 9 script requests.

The load time was actually 0.1s faster with Clarity. That is probably just an anomaly.

I have disabled Clarity for now. But may reenable it after I have made some changes to the website, to see the effect of the changes. Overall I was quite impressed with the service and it was surprisingly easy to set-up. But the cynic in me does wonder what exactly Microsoft is getting out of it.

Running Qt apps on M1 ARM Macs

Apple is switching the processor architecture of it’s Macs. Again (I transitioned PerfectTablePlan from PowerPC to Intel some hears ago). This time to their own M1 ARM chips. Reports so far have been very positive about speed and battery life of the new processors. Obviously most current Mac software has been written for Intel Macs, so they are using the Rosetta2 emulation layer to run apps compiled for Intel Macs on the ARM chips. I’m not sure how much of a performance hit this causes, but clearly it would be better to run native ARM binaries on an ARM machine. Also Apple, being Apple, want to move everyone to ARM as quickly as possible. Tough luck if you just spent big bucks on a shiny new Intel Mac.

One of my customers emailed me that the latest version of my Hyper Plan visual planner, built with Qt 5.13.1, didn’t run on an new M1 Mac. I don’t currently have an M1 Mac to test it on. But my Easy Data Transform software , built with Qt 5.15.2, apparently works fine on an M1 Mac. So I recompiled Hyper Plan using Qt 5.15.2, and was told it now works. I have found a couple of minor differences in behaviour between Qt 5.13.1 and 5.15.2, but they are too obscure to go into here. Some Qt apps may still have issues on ARM.

Currently Qt is only available as Intel binaries. Efforts are in progress to be able to build Qt as M1 (ARM) binaries. When that is complete it should be possible to ship Qt applications as a ‘fat binary’ with both Intel and ARM executables, as I did with the PowerPC to Intel transition. I’m not sure if this is going to be supported on Qt 5 and 6 or just Qt 6.

Issues with Qt applications on macOS 11.0 (Big Sur)

In my previous post I wrote about the trials and tribulations of upgrading my iMac to macOS 11.0. Here I am going to list some of the issues I know about deploying Qt applications on macOS 11.0. More issues may subsequently come to light.

The QFileDialog::DontConfirmOverwrite flag is ignored when passed to QFileDialog::getSaveFileName(). Which means that you can’t use this flag and handle the message yourself, or you will end up with a double warning. This seems to have been an issue since macOS 10.15. It still isn’t fixed in Qt 5.15.2. It is annoying, but relatively easy to work around. The Qt bug report is QTBUG-39791.

QMessageBox::information() shows a placeholder icon instead of the information icon:

It is only cosmetic. But it looks shonky and Mac users tend to care a lot about this sort of thing. I can reproduce it in Qt 5.15.2. I don’t know of a workaround. The Qt bug report is QTBUG-88928.

QMessageBox::warning(), QMessageBox::information() etc show the default focus button incorrectly. For example:

QMessageBox::warning( this, "App", "text", QMessageBox::Ok|QMessageBox::Cancel, QMessageBox::Ok );


Again it is only cosmetic, but it looks jarring. I can reproduce it in Qt 5.15.2. I don’t know of a workaround. The Qt bug report is QTBUG-89133.

There are also some other styling issues. The Qt bug report is QTBUG-86513.

Dark Mode still doesn’t work properly for Qt apps.

There was an issue on macOS 10.15 where using QFileDialog::getSaveFileName() to save over an existing file could cause a crash. Thankfully that doesn’t seem to be an issue in macOS 10.11. The Qt bug report is QTBUG-83342.

Unfortunately issues with Qt on Mac are nothing new. I realize it is a big challenge for the Qt developers to keep such a large codebase up-to-date with so many continually evolving platforms. But the Mac version always feels rather neglected compared to the Windows version. I wish they would prioritise basic issues such as the above over adding whizzy new features, 80% of which most Qt developers probably never use. macOS 11.0 was released a couple of weeks ago and betas have been available for a while.

I would be interested to hear of the experience of other developers with macOS 11.0. Any other Qt macOS 11.0 issues I should know about? Please let me know in the comments.

Upgrading to MacOS 11.0 (Big Sur)

It is always a bit of fraught process upgrading a computer OS, especially for a development machine with loads of tools and libraries installed. So I try to do it as infrequently as I can get away with. On Windows I generally buy a new PC rather than upgrade OS. However glitches had been reported in Easy Data Transform on macOS 11.0 (Big Sur) and I wasn’t ready to abandon my 2017 iMac, so I decided to bite the bullet and upgrade it from macOS 10.13 to 11.0.

The initial upgrade of OS was straightforward enough. But when I tried to run Qt Creator the CPU shot to 99% and stayed there, making the machine unusable. A glance at Activity Monitor showed that several XCode related processes were going crazy. After a bit a Googling I managed to find this magic incantation to type into the terminal on a forum post:

defaults write DVTDisableMainThreadChecker 1

I was then able to rebuild my Qt-based products: Easy Data Transform, PerfectTablePlan and Hyper Plan using the existing installs of Qt 5.13.1 and Qt Creator 4.8.0.

I had to update some of the software I use:

  • DropDMG
  • Beyond Compare
  • SnagIt

Annoyingly, I had to buy an upgrade of SnagIt as the 2018 version doesn’t work on Big Sur. Even more annoyingly the upgrade costs nearly as much as a new licence, which feels predatory.

The Subversion command line no longer worked from the terminal, but that was easily fixed by adding /Applications/ to PATH in my .profile.

So far I haven’t been able to get the following to work:

  • XCode
  • Hammer4Mac

XCode 10.1 falls over if I try to start it. It says that it requires additional components and then fails to install them. I may upgrade XCode at some point. But I only use the compiler from the command line via QtCreator, so it doesn’t really matter at present.

Hammer4Mac is a static website builder I use to build the PerfectTablePlan website and a couple of other mini sites. I upgraded to the latest version. It starts, but returns ‘Build failed’ for all 3 websites. No clue as to why. I Tweeted the creator, but got no reply. It appears to be abandonware. If so they should really take down the Hammer4Mac website. I guess I will use it from my macOS 10.14 laptop and then eventually do the tedious job of porting those websites to Jekyll.

Hopefully I won’t have to do another major upgrade of macOS any time soon (I may buy a new Mac next time).

Microsoft PPC broad match goes nuts

With Microsoft Advertising (formerly Bing Ads) you can bid on 3 different match types: exact, phrase and broad. These match types take a successively broader interpretation of your search term. Broad match has to be used with care, but can be useful for casting a wider net. However broad match seems to have gone a bit nuts for Microsoft pay per click. Witness these recent results:

broad match keywordsearch term matched
perfecttablecore muscle machine
perfecttablemy plate
perfecttableto be able to
perfecttablenumista threepence
plans tableofloxacin tablets
table plan softwaresmall business software
table plan softwarehr software for small business

What!? How is “numista threepence” in any way a match for “perfecttable”? That is stretching the concept of ‘broad match’ to weird levels. I have deleted most of my broad match keywords. Is anyone else seeing this?

How I finally beat my son at a computer game

TL;DR: I cheated, using programming.

I play computer games with my son. But he is 14 and I am 54, so I just can’t compete on reflexes. Just yesterday he thrashed me 10-3 at the silly and fun Spelunky Deathmatch. Then he gloated about my pitiful score.


We’ve also been writing our own games together in Python, for fun and so that I can teach him some programming. We’ve written a little jet dogfight game together. You each get a little plane that can turn left, right, accelerate or shoot. You score 2 points for shooting down your opponent and 1 point for flying over a powerup. First to 20 points wins. We are both Python novices (my day job is writing software in C++), so the program is quite hacky. Lots of globals and cut and paste. The planes are triangles and the clouds are square. But it is a fast and fun game to play.


Predictably my son was winning most the games. Then gloating about it. However I had recently seen an article about an AI winning dogfights against a human fighter pilot. This gave me an idea. While he was asleep I modified the program so that you can press a key to toggle a cheat mode on either plane. In the cheat mode pressing the left key aims automatically at the powerup and pressing the right key aims automatically at the opponent’s plane. Suddenly I started thrashing him. He got suspicious and insisted we swap planes. Which is fine, I just toggled cheat mode on the other plane. He got even moreĀ  suspicious. I told him I had been practising. He went off to practise with an old version of the code I gave him. I then thrashed him several more times and told him I have being doing a lot of practise. ;0)

Sooner or later he will figure out what is going on. I’m not sure what the take away lesson will be. Coding is a powerful skill? Don’t trust your Father?

I offer up the code for any competitive Dad’s (or Mum’s) who feel they need a little help against their cocky offspring. See how long you get away with cheat mode. You can always toggle it off for a while when they get suspicious.

Notes about the game

You can download the game’s Python code.

The game runs from inside the Python variant of the free environment, which you can download here for Windows, Mac or Linux. You need to select Sketch>Import Library to get the Sound library. File>Open the .pyde file in and press the run button. You can adjust the szx and szy variables according to your screen size. There seems to be a bug where the sound only works for the first game after you start the IDE.

The keyboard controls are:

Player 1:

a – turn left (aim for powerup in cheat mode)

d – turn right (aim for opponent in cheat mode)

w – accelerate

s – fire

z – toggle cheat mode (off at start)

Player 2:

left cursor – turn left (aim for powerup in cheat mode)

right cursor – turn right (aim for opponent in cheat mode)

up cursor – accelerate

down cursor – fire

end – toggle cheat mode (off at start)

Currently the aim cheat aims at where the opponent’s plane is. To be a bit more sophisticated, it could aim at where it thinks the opponent’s plane will be. But the current approach turns out to be good enough, and less likely to make an opponent suspicious.

It would be interesting to write a little AI that completely controls the plane and then put it up against other people’s AIs. A future project perhaps. But isn’t an ideal environment for that.