Scalable user interfaces: phone, pad, and desktop

This is a brief note, at a high level about designing the GUI of an application for portability across platforms (of different screen sizes and pointer devices.)  Bottom line: use context menus.

This is mostly about small, focused, document centered apps, not large, complex, bloated apps.

Skins

Skins are look and feels, or views of your app (but more than just decorations.)  Use the MVC paradigm to separate the model from the view so that you can reskin your app.

Even if you don’t offer the user a choice of skin, you probably will reskin your app during its lifetime.

Skins can vary by platform.  Here I mostly discuss trying to design one skin that is cross-platform.

Actions are central to reskinning.  Actions are fundamental things the user can do.  They are in the view, but they are linked to a method in the model.  Actions in the view can be shared (without change) across many skins.  For example, an action can be associated with a toolbar icon, a context menu item text, or button in a dialog.

Context menu GUI’s are cross-platform

A context menu driven app is mostly cross-platform.

A context menu is also known as a pop-up, or action sheet.

A context menu actually varies across platforms:

  • phone: slides up (drawers) from the bottom (iOS), NOT at the pointer (touch)
  • pad and desktop: appears at the pointer, or with a keyboard key (Win) at the selection

The notion of context is tied to the notion of selection.  The context is of the object that the user designated but the object need not become a long-term selection.

An alternative to a context menu is a toolbar (toolbars are context sensitive, their contents change according to the user’s designated place in a hierarchy or place in the app.)  The UI problem with toolbars is that they are away from the focus, unlike context menus.  On a phone, context menus are also ‘away’ from the focus (not at the pointer), but since the screen is small, they are not far away.

Document centered apps are NOT cross platform

Many apps on desktops are document centered.  Conversely, few apps on mobile platforms are document centered.  This is mostly because mobile devices are used for communicating and browsing, and are hindered  by sandboxing between apps.

An exception is photos as documents: most mobile devices are also cameras.  Many mobile devices are photo centered: the OS supports save, open, and edit photos.

This is changing: iOS8 is beginning to support document centered with the notion of document providers.

Document centered and navigation

Mobile platforms support navigation using navigation bars (when an app has hierarchical data or nature, e.g. a music library.)

A file system having documents is also hierarchical and amenable to a navigation based UI.  The problem is that on mobile platforms, the file system is not usually navigable (there is no native file browser or Finder and app files are sandboxed i.e. segregated.)

A cross-platform document centered GUI

On mobile platforms, for  document centered apps, one possible navigation-based  GUI comprises three paged views, where the navigation bars for the views are :

  •                 Docs          Edit>
  • <Docs     document   Meta>     where ‘document’ is the title of the current document
  • <document   Meta

The first view lets the user browse the app’s private store of documents (or in upcoming iOS8 apps, the private data stores of all apps that provide the mimetype which the current app understands and consumes.)

The second view lets the user edit a document, in WYSIWYG (the user sees the document.)

The third view lets the user change meta data of the document (e.g. the style or non-visible attributes.)

So, to make a cross-platform, document centered app,  you use different skins:

  • mobile: a skin such as the above
  • on the desktop, a traditional skin having a menubar with a File menu and File Chooser dialog (instead of the first view above) and with a File>Properties or other menu item for the third view above.

Share: the OS as a clearing-house, broker for documents and apps that understand them

An OS understands what apps can publish and subscribe to document types (mimetypes.)

This is done at installation time, via resources in the app’s package:

  • iOS and OSX: document icons in the bundle
  • Android: ??? in the manifest
  • Linux: desktop ??? in the .deb package

Mobile platform OS’s also support the notion of ‘share’, with a native dialog that shows the user which apps can understand your mimetypes.  The desktops (and frameworks on top of them) don’t provide that dialog.

Sharing is broad, meaning either:

  • open with another application, right now (print, or view in another application)
  • send a copy to another place (a website, the cloud, or a file system)

You can see that in the iOS share dialog, separated top and bottom.  Loosely, the top is ‘send to another place’, the bottom is ‘open with another application’.

Sharing is similar to drag and drop, except it crosses devices and has different syntax (order of steps):  with drag and drop, both applications must be open or have an icon on the screen.  So you must decide what the target is, open the applications, then sharedrag.  With share, you don’t decide first; you might decide the target after you start the sharedrag.  The target application need not be ‘open’ (displaying, because the mobile platforms have only one active window.)

I suspect that the notion of share will be soon supported by desktops (OS or frameworks), precisely because it would simplify the development of cross-platform apps.

Embellishing a skin with menubar on the desktop

A menubar is absent from most mobile platforms.  But, an apps skin on the desktop might in addition have a menubar.

A menubar is a necessary wart on desktops: when you have many windows, the menubar tells you which window is active.

A menubar also helps a user because it might be redundant: it might include all the same actions that the context menu system does, but all in one place.  A user can navigate in the menu system without navigating in the other app views.

A menubar also has global actions such as undo and redo.  A notable wart of iOS is that there is an action for undo (shake) but no global action for redo (AFAIK that requires using the virtual keyboard.)  Android also lacks undo/redo.

 

Understanding QML, prototypal inheritance, and generalization/specialization

This discusses QML in object oriented terms, for someone who is familiar with classes and unfamiliar with declarative programming and prototypal inheritance.

This was quickly written, and could be wrong.

Thinking of QML Components as classes

QML has no classes (which are first-class objects).  However, you can loosely think of component files as class definitions.

For example you have a directory:

foo
  Foo.qml

which defines a component.  (In QML, every .qml file must have only one root item, the component that is being defined.)  You can think of this as class “Foo”.  Note that this enforces the common programming practice ‘one class per file’ and it eliminates the tedium of naming a class AND the file that contains the class definition:  the filename IS the class name.

Like a class, you can instantiate it many times:

import "foo" as MyFoo

Item {
 id: item1
 MyFoo.Foo{}
 MyFoo.Foo{}
}

(Glossing over the details of how you might identify and reference the two separate instances.)  The two instances are of the same type (they have the same properties.)  The instance “item1” has a new type, an unnamed type.  (Making this code a named component (loosely speaking, class) only requires that you put it in a file whose name starts with a capital letter.)  You can say the two instances have the class Foo, but the class Foo is not a first-class object: you can’t pass it around, except as the string name of the file that defines it.

Likewise, you can think of the directory “foo” as a module, and the directory name and the module name are one-and-the-same.

Specializing generalizations in QML

Suppose you want Foo to be a generalization, and to specialize instances of it.

Note that specializing instances creates new types (derived types but not subtypes?).  It is NOT like creating subclasses?  (I should refresh my reading of Bertrand Meyer’s discussions of the orthogonality of generalization and inheritance.)

QML has two “kinds” of properties:

  • non-visual, sometimes called ‘resources’
  • visual, often items in the list value of a property named ‘children’ (which QML magically hides using default property, all of which is a digression which I won’t discuss in detail.)

Specializing non-visual properties of generalizations in QML

It is often relatively easy to specialize an instance’s non-visual properties.  First define a generalized component (class?):

//File: Foo.qml
Item {
  property var specializableProperty
}

Then create a specialized instance:

//File: Bar.qml
import "foo" as MyFoo

MyFoo.Foo { specializableProperty: "specialValue" }

This creates an instance of Foo whose specializableProperty is bound to string “specialValue.”  You might think of this a procedural parameter passing.  Except that QML is mostly declarative (except for Javascript functions.)  If “specialValue” was not a string literal, it would seem less like parameter passing.

A more illustrative example (and a common use case) would be if the name of the property were ‘model’  and you bound it to a more elaborate object (say of class Model or the QML predefined type: ListModel) than a simple string.  Then you are creating instances of Foo, specialized by having different models.  (Where the use case is: using the model-view-controller paradigm.)

Specializing visual properties of generalizations in QML

Summary: use a QML Loader.

Specializing with visual components is complicated by the fact that visual components are frequently trying to bind their properties to properties of their parent.  In other words, the object you are specializing with must be instantiated later, when the specialized component is instantiated, not at the time you are ‘passing’ the specializing component.  First create a component whose contents are loaded from a url at instantiation time:

//File: Foo.qml
// to be specialized
Item {
  property var specializedVisualPropertySourceURL
  Loader {
    id: specializedVisualProperty
    source: specializedVisualPropertySourceURL
  } 
}

Then create a specialized instance:

//File: Bar.qml
import "foo" as MyFoo
MyFoo.Foo {specializedVisualPropertySourceURL: "../baz/Baz.qml" }

where Baz.qml defines some visual component.  Here you are passing the name of the file that defines the specializing part (you are passing a class as a factory.)  You are passing it into the specialized component, to its Loader.  The Loader instantiates the specializing part in the context of the specialized component (said context having many properties such as ‘anchor’ that are needed by the specializing visual component part.)

If you try to use the same technique as for non-visual specialization, you might get errors such as “anchor not defined”.  These errors come when you are instantiating the specializing stuff so that you can pass it: at that time, the context may not be a visual component having the needed properties such as anchor.

(Here I should have example wrong code.)

Note there are many ways to skin a cat.  I think you could also use property aliases in the above code, for example to expose Loader.source directly to the specializing component.

Switching to QML from QWidget using Qt and PyQt

This is high-level, loose assemblage of discussion and links.

Context: you have a cross-platform app written using Qt and PyQt.  It is written using QWidgets.  You want to switch from QWidgets to QML.

The discussion is not exclusive to PyQt.  Much of the discussion also applies to apps writtent in C++, but mentions PyQt also.

This might also have some relevance for apps written purely in QML/Qt Quick.

Why switch to QML from QWidget?

These are possible reasons:

  • QML uses native platform style whereas QWidget fails to have native look and feel on some platforms (iOS)
  • QML more strongly follows the ModelViewController paradigm
  • QML is declarative (more compact and dense)
  • a QML GUI is easier to change (to provide multiple or improved GUI’s for the same business model)

To me, the first reason is the most compelling.  The last reason is least compelling: you probably won’t be changing the GUI often (but then here we are talking about changing at least the implementation of a GUI.)

(Qt apps are excluded from the Windows Store because Qt did not support Windows8/WinRt but Qt5.4 and QML may remedy that?)

Model View Controller paradigm

Qt documentation: Model – View Options in Qt

Wiki: Model–view–controller

It is quite likely that your existing QWidget apps does not strictly adhere to the MVC paradigm.  Thus you will be probably need to refactor your code: separate model code from view code.

A strategy for  developing (code changes) to switch to QML from QWidget

Assuming your app using QWidgets already runs and has tests, a good strategy is to keep the QWidget implementation of the View working, while you develop the QML implementation of the View.   In other words, use test driven development: make small changes, then test the QWidget implementation AND the QML implementation.  The QWidget implementation should always remain fully functional after each change.  The QML implementation will also always work, but in a stubbed-out, minimal fashion.

Changing the existing QWidget app will largely entail:

  • splitting classes along model/view lines
  • converting procedure calls to signals, connections, and properties

Swapping in the QML implementation of the view will be just a small change in the startup code: creating QML instead of QWidgets.  (Its a little more complicated than that, you might be making connections in the QML instead of in the main app.)

Embedding QML in a QWidget app

You can implement the main app window (and all other GUI) in QML.  But you can also embed QML GUI inside a QWidget app.

Briefly, it entails using QQuickView.  But a QQuickView not is-a QWidget.  But you can wrap a QQuickView in a QWidget:  Qt documentation: Introducing QWidget::createWindowContainer() discusses how to wrap a QQuickView in a QWidget.

Why retain a QWidget shell embedding QML?  I suspect there are few reasons to do so.  One reason might be just the strategy discussed above; get your feet wet by embedding QML and make the last step converting the main window from QWidget to QML.

My reason was different: my app uses elaborate UI  picking of objects from a QGraphicsScene.  The QML scene graph doesn’t expose the same API for picking that the QGraphicsScene does.

More on the interface between model and view using QML

Qt documentation:

Where ‘from C++’  more generally means ‘from model objects’ .  And the discussion applies to model objects written in PyQt.

The interaction is two-way, bidirectional:

  •  Control (in MVC) is from the view to the model
  • View is from the model to the view

Your interaction may be one direction or both:

  • a weather app is just a viewer of the weather
  • a styling app is just a controller of a document

Much of the logic in the QML is not business logic, just GUI logic.  For example, a button may switch pages and have no effect on the business model.  The button ‘controls’ the view, but not the business model.

Not all the interaction need be between the model and the view.  For example, if you are embedding QML in a QWidget app, the QWidget app may need to simply activate (show, make visible) a QML view component.  In other words, the interactions may be between the part of the view implemented in QWidgets and the part of the view implemented in QML.

PyQt and QML

PyQt fully supports QML.  You can use Python and PyQt for your model.

PyQt documentation:  Integrating Python and QML

I have shared a hacky Github project where I learn and test PyQt and QML.

For the same reason you use QML for the view, you should consider Python for the model (more expressive in fewer lines of code.)

The QML tradeoff: more portable but less native?

Typically, a cross-platform solution makes certain compromises.  Sometimes its discussed using terms such as “least common denominator” or “adaption.”

I can’t fully discuss this here.  (And probably the set of compromises that have been made are not fully documented in one place.)  But you should consider whether you will be able to live with any such compromises.

For example, I found that a QML Dialog on a desktop (Linux, I haven’t tested OSX) opens in a separate window, not on top of (transient to) the main window.  That probably doesn’t meet the HIG’s for desktops.  Possibly there is a solution that I haven’t yet found.  Or possibly I should look at that as a feature to embrace: I should make my dialogs non-modal and treat them as tear-offs that should not obscure the main window anyway.  But its an example of the compromises you might need to make.

There is much more to discuss here.  Briefly, Qt Quick Controls does use native widgets where possible, then falls back to QWidget implementations if possible, and then to a pure-QML implementation.

But again, its hard to know in advance without trying it.

Structuring your QML source code

In many examples, you see long QML files.  I  think that is an artifact of being an example: put everything in one file for ease of reading.  You can structure you QML source files into smaller files in more directories, using imports.

Qt documentation: Import Statements | QtQml 5.3

Packaging your app

I don’t foresee any show stoppers here.  Just more to learn.  If you are already packaging a PyQt app, you probably are already packaging resources using pyrcc.  The QML is just another resource to package.

But, I’m not sure how third-party QML plugins get packaged?

A strange architecture: PyQt and QML uses two interpreters

A PyQt app using QML uses two interpreters: the Python interpreter and QML’s Javascript interpreter.  (And on Android, there is a third Java interpreter.)  There is much discussion about the safety of, and constraints applied by app stores, re interpreters.   But this is just a curiousity, there are already shipping apps, using interpreters, in the stores .

A template for QML using models

Qt’s example Weather App is a starting place for reading code: Cross-Platform Applications in iOS and Android Stores with Qt

(I hope to reduce it to a simple template.)

Desktop to Mobile GUI difference: sharing

The main problem that I have found is that for certain apps (document editing or creation apps as opposed to reading or browings apps) the desktop:

  • exposes the file system to the user (in the File menu)
  • uses drag and drop

whereas mobile platforms use the notion of ‘sharing’ to the file system and other apps

Notably lacking in Qt Quick Control is a ‘share sheet’.  A github project QtSharingKit seeks to remedy that, with a QML plugin.

Pragmatically, that means more work for you:

More links about QML, Qt Quick, and Quick Controls.

You use these technology pieces:

  • QML a language.
  • Qt Quick: the standard library of types and functionality for QML
  • Qt Quick Controls: canned (pre-built) QUI controls built using QML
  • QML Applications

Using pyqtdeploy on MacOS to cross compile a PyQt app for iOS: part 3

Rehash

This is part three of three parts:

  1. install Qt and Xcode, then test build and deploy an example Qt app for iOS
  2. cross compile static libraries for iOS (using pyqtdeploy.)
  3. use pyqtdeploy to cross compile and package your app.

Overview

As discussed in Choosing a Toolchain, there are many possible toolchains.  In previous parts, we only compiled static libraries for the ARM architecture of real iOS devices.  In this example, we will use the pyqtdeploy/Xcode/real iOS device toolchain.

The steps are:

  • use pyqtdeploy GUI
  • sign up and pay for an iOS developer
  • connect and configure your real iOS device for development
  • use Xcode to deploy
  • run app on real device

Pyqtdeploying your app for target iOS

Here you use the pyqtdeploy GUI app to prepare projects, makefiles, and source code.  This is very similar as for any target.

Connecting and configuring your real iOS device

Hook up your device with a USB cable.

What follows is highly automated.  Xcode should start and display the Organizer, featuring your device

Briefly, wait for it to process a while, then choose the ‘Configure for Development’ button, and so forth.

(Dated, confusing, and slightly conflicting instructions are given here.)

Compiling and packaging your app for target iOS using Xcode

Assuming you ran qmake step in pyqtdeploy, it created an xcodeproj.

Open that project in Xcode.

Configure Xcode to deploy to your real device: click in the left side of the title bar where it may say ‘Foo project>iosSimulator’ ??.  A list of devices (real and simulator) should appear.  Choose a real device.

Choose Product>Run.  A few dialogs may appear:

  • allowing codesigning to occur
  • to tell you to unlock your sleeping device using its four character passcode.

This deploys your app, updating it if it was deployed previously.

This also runs your app, displaying stdout and stderr within Xcode.  Your app will become active (displaying its windows) on the device.  Touch the device to generate input events and debug.

Run your app later

On the real device, pan left and right in the display of apps to find your app.  (It probably has a default, nondescript icon.)  Tap to run it.

FAQ or Notes

No QPrintSupport module of Qt.  If you import PyQt5.QtPrintSupport, you get an error at link time.  Unlike on Android where I did not seem to get a link error (but only a crash?)  Apparently Qt does not yet support printing on mobile devices.

Debug on iOS first.  In my opinion, because Xcode generates better error messages at link time, and because Xcode displays your program’s stdout and stderr in a window (unlike using QtCreator on Android? I could not easily get it to work.)

If your app was designed for the desktop, it may work since the QtApplication flag about translating touch events to mouse event defaults to ‘translate touch to mouse events.’  If you change the default, it seems like some widgets e.g. menus and buttons will not work (they depend on mouse events?)

Notes on pyqtdeploy

You should read the official documentation, but it is brief.  Here, I elaborate:

  • discuss general concepts that seem obvious to experienced developers
  • discuss specific commands

About pyqtdeploy

(See my other platform specific posts, I plan to consolidate sections here.)

Installing and upgrading pyqtdeploy

The pyqtdeploy download page is not really a place to download.  It says that you can:

  • install the latest stable release using pip3
  • install the latest development version using mercurial (hg)

Managing pyqtdeploy using pip3

Installing pip3

pip3 is needed to install stable pyqtdeploy

  • OSX: pip3 is installed with Python3.
  • Ubuntu: if pip3 is not already installed:
sudo apt-get install python3-pip
Installing latest stable pyqtdeploy
pip3 install pyqtdeploy

(If that fails on permissions: sudo pip3…. )

Upgrading stable pyqtdeploy

Pyqtdeploy is released frequently.  To upgrade to the latest stable release.

>pip3 install pyqtdeploy --upgrade

Soft linking to pyqtdeploy

It seems that the above does not put pyqtdeploy and pyqtdeploycli executables in the path.  You might need to make soft links:

>cd /usr/local/bin
>sudo ln -s /Library/Frameworks/Python.framework/Versions/3.4/bin/pyqtdeploycli pyqtdeploycli
>sudo ln -s /Library/Frameworks/Python.framework/Versions/3.4/bin/pyqtdeploy pyqtdeploy

Managing your development version of pyqtdeploy using Mercurial

Note that mercurial uses Python2.7 but pyqtdeploy uses Python3.

Installing pyqtdeploy using hg

>hg clone http://www.riverbankcomputing.com/hg/pyqtdeploy
>cd pyqtdeploy          # to the directory you just cloned
>make VERSION           # ? not sure why next two steps are not dependencies in the Makefile
>make pyqtdeploy/version.py
>sudo python3 setup.py install

Again note that we install in python3 site packages.

Updating your development version of pyqtdeploy
>cd ~/Downloads/pyqtdeploy*
>hg pull
>hg update
> ??? make VERSION
>sudo python3 setup.py install
Installing Mercurial

You need Mercurial to download development version of pyqtdeploy.  Mercurial is a version control system.  Also known as the ‘hg’ command.

The Mercurial downloads page.

Note that Mercurial uses Python 2.7 and doesn’t support Python3 but that’s usually OK since Python-2.7 is usually installed.

Pyqtdeploy is now two tools

Starting with version 0.6, there are separate tools:

  • pyqtdeploycli: a command line tool for building static libraries
  • pyqtdeploy: a GUI tool for building your app

(I suppose you could also use pyqtdeploycli to build your app, passing it a configuration file.  But using the GUI pyqtdeploy is much more intuitive and easy.)

Disambiguating deploy

The pyqtdeploy tools don’t actually deploy.  They just prepare for deploy.  They may create or touch:

  • source files (.c, .cpp and .py)
  • qmake projects (.pro)
  • make files (Makefile)

Subsequently you must continue to build using qmake, make, and other tools such as Qt Creator and Xcode.  Then you have a binary, executable app.

Qt Creator does not deploy to the App Store

You must read QT Platform Notes – iOS very carefully.  It says that Qt Creator will do everything for building a Qt iOS app except:  deploy to the App Store.

You don’t need to use Qt Creator, you can use only command line tools (and then possibly the Xcode GUI.)

But this is related to the difference between the iosSimulator and a real device: they use different ABI architectures.  You should think early about whether you will use Qt Creator, and whether you will use the iosSimulator or a real iOS device.  You must fork the process of building early, when you are building static libraries (for either iosSimulator/ i386 architecture, or a real device/ ARM architecture.)

(Will Qt Creator deploy to Google Play store?)

Choosing a toolchain

This is related to choosing a machine for deployment.

Ideally, you learn and use one toolchain that works for every platform (avoid the learning curve for some tools.)

  • use pyqtdeploy/qmake/Xcode/real iOS device for iOS
  • use pyqtdeploy/qmake/Xcode/real Android device for Android

But for Android I have also used, with mixed results:

  • pyqtdeploy/Qt Creator/real Android device
  • pyqtdeploy/qmake/command line scripts/real Android device

I’m not sure whether I need to learn and use the emulator/simulator tools to do sanity testing across device models (e.g. iPhone vs. iPad) and OS versions (e.g. iOSv7 vs iOSv8.)

Deploying PyQt Python Qt apps cross platform using pyqtdeploy

These are general notes and links for this subject.  In other words, this is a table of contents.  Many links are often specific to a target platform.

Some of the links to my posts are to incomplete drafts.   Also, I edit these blogs periodically, since pyqtdeploy is actively developed and I am learning too.  It is best to consult source documentation.

Cross-platform and Localization/Internationalization/i18n

You want the largest possible audience or market.  You are using Qt because it:

  •   is cross platform.
  •  is cross language: supports localization ( internationalization)

You increase your audience by porting to new platforms AND by localizing to new languages.

Contents

Original (source) documentation

pyqtdeploy v0.6 User Guide

Qt for iOS

Qt for Android

General

Cross platform software development

More notes about pyqtdeploy

Deciding between Xcode and QtCreator for packaging PyQt apps for OSX

Target Linux and MacOS

Using pyqtdeploy

MacOS bundling (packaging) a pyqtdeployed app using Xcode

 Target iOS

iOS part 1 (prepare tools)

iOS part 2 (cross compile static libraries)

iOS part 3 (cross compile app and deploy)

 Target Android

Android cross compiling Qt example C apps

Android part 1 (prepare tools and cross compile static libraries)

Android part 2  (cross compile app)

Android part 3 (deploy and debug) DRAFT

Localization

General notes

Localizing PyQt app for target OSX using Xcode

More localizing PyQt app for target OSX

Using Qt Linguist phrasebooks

Adventures in translating an app to Chinese

Universal user interface

Commands that support gestures

Using pyqtdeploy0.5 on Linux to cross compile a PyQt app for Android: Part 3

Rehash

You have:

  1.  set up your dev machine and built static libraries
  2. pyqtdeployed your app so it is ready to deploy

This part discusses:

  • deploying your app to a test target machine
  • debugging your app

More about pyqtdeploy work flow

The pyqtdeploy GUI lets you choose your work flow.  That is, in the ‘Build’ step, you can choose optional steps: qmake, make, ‘run app’.  For example, you can choose none of those, and Build will only create a .pro file.

Then you can switch to:

  • the command line
  • or Qt Creator (a GUI app)

and resume the work flow (qmake, make, deploying, and running.)

(I’m haven’t tried  ‘run’ in pyqtdeploy for target Android, I always switch to another tool.)

pyqtdeploy developer notes

The pyqtdeploy directory contains a file DeveloperNotes.txt.  These notes are by the developer of pyqtdeploy  but are useful hints about Android development.  The author of the notes has said the notes are NOT definitive and should be used with care.  I have found  typos there.

Deploying using the command line

Follow the instructions in the developer notes.   I.e. :

# Assert you ran qmake and make in pyqtdeploy, but not run

# cd to dir that pyqtdeploy created
cd ~/hellobuild/build
export ANDROID_NDK_ROOT=$HOME/android-ndk-r10
export ANDROID_SDK_ROOT=$HOME/android-sdk-linux
# configure install
make install INSTALL_ROOT=deploy
# make deployable
/home/bootch/Qt/5.3/android_armv7/bin/androiddeployqt \
 --input android-lib*.so-deployment-settings.json \
 --output deploy
# deploy
adb install -r deploy/bin/QtApp-debug.apk
echo Now go to AVD and run app

This only installs your app to a target device.  You subsequently must use the target device’s GUI to run your app.

If you get:

Failure [INSTALL_FAILED_ALREADY_EXISTS]

this means you omitted the -r flag, which means reinstall.  An alternative is to manually uninstall the app first (on the device, choose Settings>Apps, then select your app, then choose the Uninstall button.)

Deploying using Qt Creator

When you choose the ‘Run’ icon in Qt Creator, it builds, deploys, and runs your app.  So it is more automated, but harder to understand what Qt Creator is actually doing on your behalf.

Debugging pyqtdeployed apps on android

First, refresh the tools

The tools (especially pqytdeploy, SIP, and PyQt but also Qt and Python) are updated frequently.  You should use the latest versions, which may have fixed some Android crashes.

Remember that pyqtdeploy is the tool that helps build static Python, etc. libraries.   So if you update pyqtdeploy, you should probably rebuild static Python starting with invoking pyqtdeploy (so that any new patches known by pyqtdeploy are applied.)

Stderr and Android logcat

Unfortunately, Python prints exceptions to stdout/stderr.  These by default are dedirected to /dev/null on Android (which means they disappear.)

On android, the system log is called ‘logcat’.  To see it remotely:

>adb logcat > logcat.tmp
>Ctl-C
>vi logcat.tmp

Its a circular file.  Fileness means it persists (even across your debugging sessions.)  Circular means it wraps around, overwriting older data. Pragmatically, one debug session never seems to wrap around on itself.  The end of the file is always the latest log entries?  You can clear it by rebooting the device (powering it off.)

Redirecting stderr to logcat

You should be able to configure your target device to redirect stdout and stderr to logcat.  To do this requires:

  • a rooted real device
  • an AVD (virtual device)

An AVD is always rooted (you have superuser privileges to bypass all security and do what you want).

Rooting a real device is an adventure unto itself, so I chose not to do it yet.  But the advantage is that a real device eliminates the simulator layer, and is quicker to boot and so forth than an AVD.  You can still do certain debugging on an un-rooted real device, you just won’t be able to see Python exceptions directed to stderr.

!!! If a device is not rooted, the adb commands to redirect stdio fail quietly.  So you still won’t see stderr in the logcat.

To do it you can:

  • issue adb commands
  • create a persistent configuration file

adb commands to redirect stdout/stderr

>adb shell stop
>adb shell setprop log.redirect-stdio true
>adb shell start

Remember this doesn’t persist if you reboot the device (whether and AVD or a real device.)

If the device is rooted real device, you must precede that with >adb root.  (AVD’s are always rooted, so >adb root is not necessary.)

Debugging methods

Traditional choices:

  • use a debugger such as gdb (which is built into Qt Creator)
  • harness code with print statements
  • simple code analysis using crash dumps and Python exception tracebacks

It depends on where the bugs are:

  • in the Python interpreter
  • in your pyqtdeploy build process
  • in your app’s Python code

It is not uncommon to find bugs in the Python interpreter, since the Android platform is not supported by Python.org.  (the pyqtdeploy process patches the Python interpreter, and you might find that another patch is required.)

The pyqtdeploy build process is intricate.  It is not uncommmon to find that you have not frozen a needed Python module (and then you might get an ImportError exception.)

Once you get as far as running your own app’s Python code, you probably have installed an exception hook, and can display the exception and traceback on the GUI of the device. (But probably, you are only porting to Android, and have worked out most of your app’s bugs on another platform, except for any Android specific code.)

Harnessing pyqtdeploy apps on android

I experienced the need to harness (since my app seemed to crash before Qt Creator’s debugger could even get started properly and without any Python exceptions.)

This means writing C code.  You can use:

  • qWarning() C function.  It is properly redirected to Android logcat.
  • __android_log foo C function.

If you need to harness the Python interpreter, you might need to study how to write Python extensions, for example to convert a PyObject * to a representation that is a C string suitable for qWarning().

I harnessed pyqtdeploy_start.cpp and Python/import.c.

Here is the code I used:

TODO

Building a debug version of Python

reading an Android crash dump

packaging using Qt Creator

Localizing (i18n) a PyQt App on OSX using Xcode

INCOMPLETE DRAFT.  I’ve done this process, just haven’t written it up yet.

How to tell Xcode to localize strings in native dialogs of a Qt app.

This is also discussed in many posts on Stack Overflow and in the Qt documentation.

This is about using the Xcode GUI.  If you are using the command line to invoke Xcode tools, or using Qt Creator to bundle your Mac app, the details may not apply.

Checklist of product components to localize

  1. user-facing strings in your Qt code (in the GUI of your app that you wrote.)
  2. user-facing strings in the Qt library (in GUI that the Qt library implements)
  3. strings in platform native dialogs (in the GUI of your app that the platform provides.)
  4. your help files (typically html)
  5. your marketing materials (uploaded to stores)

This blog is most specific about 3.

PyQt and Qt’s user facing strings

The user-facing strings 1 and 2 are embedded resources of your app.  While you could package them as Mac resources, it is more general to package them as Qt and PyQt  resources.  This doesn’t discuss how to do that, except:

  • you use the tools pylupdate, Qt Linguist, lrelease, pyrcc
  • the translation resources end up as a foo_rc.py file in your project
  • PyQt provides the mechanism for finding those resources as needed

The difference between native dialog strings and strings in your Qt code

Qt provides mechanisms to localize strings in the code that it knows about.  But your app, via Qt, can invoke native dialogs, such as the Print dialog.  Qt doesn’t know about the strings in that dialog.

(Qt does know about conventions for naming standard buttons and menu items on certain platforms.   These are in the MAC_APP??? context in Qt .ts files, etc.)

How to tell Xcode to localize native dialogs

  1. Add a localizable string to your project
  2. Add a language to your project
  3. Arrange that .lproj files are in your project and bundled
Add a localizable string

In the Navigator pane, click on the + icon at the lower right (or press RMB on your project and choose “Add New

TO DO

 

Using QtCreator on Linux to cross compile for Android

This is a log of setting up a development environment.  I am using Ubuntu 14.04.

My ultimate goal is to use pyqtdeploy to distribute a (Python, PyQt, Qt) app to Android (and iOS.)

Installing QtCreator

QtCreator is a cross platform IDE.  I have been using Eclipse and PyDev, but QtCreator might be better, since it comes from the Qt project.

( This bug report explains that the version 3.0.1 of QtCreator shipped with Ubuntu 14.04 will give the error message:

Project ERROR: You need to set the ANDROID_NDK_ROOT environment variable

so I downloaded a later version of QtCreator 3.1.0.  You can’t just use >sudo apt-get install qtcreator.

In my experience, Ubuntu lags behind on Qt, and Qt changes rapidly, so it is worthwhile to download the latest Qt.)

At this website I chose the ‘Qt Online Installer’, and the file ‘qt-opensource-linux-x86-1.6.0-4-online’ was downloaded.  Then give it execute permission and click on the downloaded file.  The ‘Qt Setup Wizard’ app opens.  Accept the defaults, and newer versions of Qt and Qt Creator will be downloaded to ~/Qt directory.  (This will include the Qt library built for ARM i.e. for Android.)

(As explained by the Qt project, Qt is supports iOS, but QtCreator doesn’t support iOS directly, you will need to use a .pro file AND the Apple Xcode IDE for that.  This blog may apply to using OSX as the host and cross compiling to Android.)

Install more tool chain

Qt Creator (when cross compiling for Android) relies on:

  • Android SDK
  • Android NDK
  • ant
  • JDK

Installing Android SDK

Navigate your browser to the Android SDK download website.

Under ‘SDK Tools Only‘, choose a package to download.  (I don’t want a package that includes the Eclipse IDE or the newer ‘Android Studio’ IDE, since I am using the QtCreator IDE.)

Extract the package, say to your home directory.

Refreshing the Android SDK

Now you have just the basics, as explained here by the Android team.  You use the ‘Android SDK Manager’ to download more.  It is a package manager.  You can start it from a command line:

>cd ~/android*/tools
>./android sdk

A GUI app will open.  It lets you browse a tree directory of packages to install.  It will open with an appropriate set (the latest) packages you need to download.  That includes:

  • Android SDK Platform-tools
  • Android SDK Build-tools
  • a version of the Android API

I unselected the latest version of the Android API (Android L, API 20 preview) and selected the next API (since I don’t want to mess with a possibly unstable API.)

Now choose the ‘Install packages ‘button.

Downloading the Android NDK

The SDK is for developing in Java.  You need the NDK for developing in C.

Navigate your browser to the Android Developer website page for downloading the NDK.  Choose the download for your host word length (32-bit or 64-bit) (in my case, 32-bit Ubuntu 14.04) and for your target Android word length (in my case, 32-bit, since I am just testing and have no specific target device in mind.)

Extract the download, say to your home directory.

Installing Ant

(I’m not sure what kind of tool ant is.)

>sudo apt-get install ant

Install a JDK

You can use the Ubuntu Software Center to find the name of the package and download it.  Search for ‘JDK’ and also choose ‘Show xxx technical items’ near the bottom.  I chose openjdk-7-jdk, but clicking the ‘Install’ button didn’t seem to work, so…

>sudo apt-get install openjdk-7-jdk

Configure QtCreator to use the tool chain: SDK, NDK, ant, and JDK

Start QtCreator and choose Tools>Options>Android.  Click the ‘Browse’ buttons and navigate to the location where you downloaded the tools.  (For many of them, Qt Creator leads you to the right place, but for the JDK I had to navigate to /usr/lib/jvm/java-7-openjdk-i386.

Configure QtCreator to use the Android kit

Choose Tools>Options>Build&Run.  Choose ‘Android for armeabi-v7a (GCC 4.8, Qt 5.3.1)’, which is auto-detected.

Create an example Project

I followed this tutorial.  I don’t really want to use QtQuick, but this is just an example.

(I also took shortcuts with the example.  Instead of displaying an SVG file and moving it, I just displayed the white rect that appears in the default QML.)

After creating the project, I clicked on the ‘Run’ icon. Now it says that I do not have an Android device.  It offered to create one for me (a ‘Create Android Virtual Device’ button.)  I clicked on that, entered a name for the device, and clicked OK.  Then I got an error: ‘Error: invalid –tag default …’  (I suppose it is a bug in QtCreator that might be finessed by different choices.)  But I decided to use Android tools to create an AVD….

Create an AVD using the Android AVD Manager tool

An AVD is a simulator, or Android virtual device.  The Android project provides a tool ‘Android Virtual Device Manager’ for creating them.

>cd ~/android*/tools
>./android avd

A GUI app will open.  Create an AVD.  (Glossing the details, but the defaults usually work, and for a clean development machine, you will only see the choices from the things you installed earlier.)

I suppose the list of AVD’s is in a standard place that QtCreator knows about.

Testing

Now in QtCreator when I click the Run button, it:

  • lets me chose the AVD I just created
  • proceeds to build,
  • starts the AVD
  • downloads my app to the AVD

It takes a while for the AVD to boot itself.  For a while you may see a black screen or a green arc going in circles.  I don’t think the app can load while it is booting.  (When it finishes booting, you can interact with the AVD like a real phone, using the mouse.   It seems to start in Android’s ‘Getting Started’ help screens (several screens telling you that a swipe does) just as if you had just powered up a newly purchased and activated phone.)

I suppose QtCreator timed out waiting for the AVD to boot.  I got:

Unable to start 'org.qtproject.example.accelbubble'.

But the AVD kept running.  I tried the Run button again.  This time, the application seemed to start on the AVD: it displayed a white rectangle.  I tried to click with the mouse in the app to close it, that didn’t work.  A swipe with the mouse (pressing the LMB while moving) killed the app in the AVD.

 

 

 

Localizing a PyQt app on MacOS OSX

Brief notes, possibly wrong, from my experience:

Your app must negotiate with OSX using QLocale.uiLanguages, to decide what translators to install.  You have translated your app’s localizable strings to a set of languages (using Qt tools for i18n.)  OSX has a prioritized list of the languages the user prefers (from System Preferences>Languages.)  Your app must find the best fit.  Your app using translators for the language given by QLocale.system().name() is not usually the right thing to do.

In Info.plist, the key CFBundleLocalizations, which appears as key “Localizations” in the Xcode GUI.  I don’t think this has any effect on how OSX treats your app.  I think it is just used in marketing, that is the stores use this to decide whether your app should be in a store in a certain country.

In OSX, a terminal always shows environment variable LANG equal to ‘en’ or similar, that is, English.  Unless you change your environment variables, or the settings of the terminal.  That is, this is not affected by the user reprioritizing languages using System Preferences>Languages.

The file locversion.plist does not seem to be necessary anymore (on OSX 10.9 and Qt5) to get a Qt app to be localized (despite what certain Qt documentation says.)

OSX knows what translations your Qt app supports only by the set of xx.lproj folders in the Resources folder of your app bundle.  For a Qt app, these xx.lproj folders will contain little if anything, just a placeholder, say a single Localizable.string, which really is not used by your app.  By “OSX knows” I mean: OSX translates native dialog strings according to.  Much of your GUI is from Qt, and is translated by Qt, but Qt displays some native dialogs (provided by OSX) and these are localized by OSX according to the presence of the xx.lproj files.

A localization has these components:

  • native dialogs (localized by platform, but depends on your bundling/packaging) e.g. “Save File” dialog
  • Qt dialogs (localized by Qt project, e.g. qt_xx.qm) e.g. standard button “Cancel”
  • your GUI face (localized by your projects .ts files)

In a PyQt projects, localizable resources will be in a myApp_rc.py file.  Your app won’t contain any localizable resources that OSX understands or needs, i.e. in myApp.app/Contents/Resources.

To test, use System Preferences>Languages.  Move a language to the top of the list and close the dialog.  Don’t restart your computer (that just means that Finder, and other apps already running, may not use the chosen language until you do restart, or unless the apps are designed to change language dynamically.)  Start your app.  All the components listed above should appear in the chosen language.