DSMenu

From Iseborn Wiki
Jump to: navigation, search

When I started with DevStick, it was with the (in hind-thought somewhat naive) intention that it would be sufficient to have Windows shortcuts to all the programs on the stick in the root directory of the stick. This worked nicely until I passed the number of about ten installed programs. Even this early in the development it became, not difficult, but unexpectedly cumbersome to keep track of the program icons.

So I determined that I need some sort of start-up application where I can, in some way, make it easier to organize all the programs to make them easier to find. After several not-so-successful attempts, I finally decided that the thing I wanted was a smallish window GUI with a tab set, consisting of HTML pages so that I can display the program icons/names exactly the way I want to.

To make a very long story relatively short, I ended up with the following setup:

  • The application called DevStickMenu, which I place directly in the DevStick root directory as DevStickMenu.exe.
  • The main menu is a dynamically generated set of tabs, where each tab is a wxHtmlWindow displaying a set of selectable application icons and names.
  • The data to fill the tabs above is read at start-up from a JSON data file.
  • The JSON data file has a default configuration, but you can modify or replace it with a changed or completely new configuration of your own.

The sections below describe different aspects of this application.

The JSON data file

General notes regarding the JSON file format described in the sub sections below:

  • The order of the members is never important unless explicitly stated.
  • The directory delimiter character in Windows is \ ("backslash"), but in the JSON data file you should always use / (forward slash) instead.
  • As usual in Windows, file names (including directories) are not case sensitive, but the asset identifiers (the assets' id attribute) are. An asset with id "Bosse" is not the same as an asset with id "bosse". So remember to use the same casing in the asset definitions as in the panel definitions. You should probably stick to all upper or all lower case (for the ids).

Top-level file item

The top-most item in the file is always a JSON object with two name/value pairs:

  • "assets": An array of DSMenu asset objects.
  • "panels": An object with DSMenu Panel definition arrays.

Pseudo JSON:

{
  "assets" : [ ... ],
  "panels" : { ... }
 }

The "assets" array

This is an array where each item is a JSON object defining a DSMenu asset. Each asset can have the following attributes:

  • id (mandatory): A unique name for the asset. This is the identity that you use for the asset when defining the panel contents.
  • type (mandatory): Must be one of the following:
    • "program": This asset is a program to run, i.e. the path will simply be executed when the asset is activated.
    • "command": Functionally identical to "program", but I want to distinguish between the two to make it possible to e.g. check if a program exists before executing it.
    • "url": This asset is a URL to open in the pre-selected browser (see XXX).
    • "document": This asset is a document to open. For example a PDF document.
  • path (mandatory): Absolute directory path and file name of the program or document to open (for types "program" and "document"), or a valid URL (for type "url").
  • args: String of arguments to be added after the path. Note: Any occurrences of the string ?: (question mark directly followed by colon) in the args attribute will be replaced by the current drive letter of the DevStick. If, for example, the DevStick has been assigned drive letter E:, and args is set to "-dir ?:/Settings", then args will be translated to "-dir E:/Settings" when used.
  • iconpath: Absolute directory path and file name of the icon to display for the asset. Preferably a .png file.
  • name: This will be the displayed link name for the asset in panels. If not specified, the id attribute will be used as name.
  • descr: A description of the asset. How this is displayed varies depending on the layout of the panel.

About directory paths (path and iconpath): You should always provide a full absolute path to the asset. Just leave the drive letter (e.g. E:) out if the asset is located on the DevStick (which it probably always is), and DSMenu will fill that in when parsing the assets. If you do specify a drive letter, DSMenu will assume that you know what you are doing and that the asset is really located exactly there.

Example: INCORRECT AFTER RECENT CHANGES ABOVE - MUST BE UPDATED

  "assets" : [
    {
      "id"       : "GoogleChrome",
      "name"     : "Google Chrome",
      "type"     : "program",
      "descr"    : "GoogleChromePortable web browser.",
      "path"     : "/Programs/GoogleChromePortable/GoogleChromePortable.exe",
      "iconpath" : "/Settings/DSMenu/images/chrome.png"
    },
    {
      "id"       : "Firefox",
      "name"     : "Firefox",
      "type"     : "program",
      "descr"    : "Mozilla FirefoxPortable web browser.",
      "path"     : "/Programs/FirefoxPortable/FirefoxPortable.exe",
      "iconpath" : "/Settings/DSMenu/images/firefox.png"
    },
    {
      "id"       : "svn-book",
      "name"     : "The Subversion book",
      "type"     : "document",
      "descr"    : "The (free) book about Subversion.",
      "path"     : "/Docs/svn-book.pdf",
      "iconpath" : "/Settings/DSMenu/images/pdf.png"
    }
  ]

The "panels" object

In this object, each name/value pair is the name of a panel (also used in the panel's tab), and an array that is the (ordered) list of ids for the assets to show on the panel.

Note that I said "ordered list", meaning that this is the only place in the JSON file where the order of anything matters. The order in which you list the asset ids here, is the order in which they will be displayed in the corresponding panel.

Example:

  "panels" : {
    "Main" : [
      "GoogleChrome",
      "FileZilla",
      "CodeBlocks"
    ],
    "Net"  : [
      "FileZilla",
      "Firefox",
      "GoogleChrome"
    ]
  }

JSON Schema for the data file

Below is the JSON Schema that defines the data file contents.

PLEASE NOTE that while this schema is correct, it is also incomplete. I have not yet finished filling out all the data in it.

{
  "type":"object",
  "$schema": "http://json-schema.org/draft-03/schema",
  "title": "DSMenu asset and panel data.",
  "name": "DSMenuData",
  "id": "http://iseborn.eu/",
  "required":true,
  "properties":{
    "assets": {
      "type":"array",
      "title": "All DSMenu assets.",
      "name": "Assets",
      "id": "http://iseborn.eu/assets",
      "required":true,
      "items":
        {
          "type":"object",
          "id": "http://jsonschema.net/assets/0",
          "required":false,
          "properties":{
            "args": {
              "type":"string",
              "id": "http://jsonschema.net/assets/0/args",
              "required":false
            },
            "descr": {
              "type":"string",
              "id": "http://jsonschema.net/assets/0/descr",
              "required":false
            },
            "iconpath": {
              "type":"string",
              "id": "http://jsonschema.net/assets/0/iconpath",
              "required":false
            },
            "id": {
              "type":"string",
              "id": "http://jsonschema.net/assets/0/id",
              "required":false
            },
            "name": {
              "type":"string",
              "id": "http://jsonschema.net/assets/0/name",
              "required":false
            },
            "path": {
              "type":"string",
              "id": "http://jsonschema.net/assets/0/path",
              "required":false
            },
            "type": {
              "type":"string",
              "id": "http://jsonschema.net/assets/0/type",
              "required":false
            }
          }
        }
 
 
    },
    "panel_order": {
      "type":"array",
      "id": "http://jsonschema.net/panel_order",
      "required":false,
      "items":
        {
          "type":"string",
          "id": "http://jsonschema.net/panel_order/0",
          "required":false
        }
 
 
    },
    "panels": {
      "type":"object",
      "id": "http://jsonschema.net/panels",
      "required":false,
      "properties":{
        "Main": {
          "type":"array",
          "id": "http://jsonschema.net/panels/Main",
          "required":false,
          "items":
            {
              "type":"string",
              "id": "http://jsonschema.net/panels/Main/0",
              "required":false
            }
 
 
        }
      }
    }
  }
}

The DSMenu C++ project

The source code for DSMenu is in this Subversion repository at Assembla.com: https://subversion.assembla.com/svn/iseborn.DSMenu/

Follow this style guide when coding this application.

Source code documentation

I use doxygen for documentation of the interfaces...

Help file

Code::Blocks

wxWidgets

wxSmith

Code::Blocks uses a plugin named wxSmith for RAD design of the GUI.

Problem:

In CB 12.11 there is a bug in wxSmith that concerns nested "sizers". I am not sure exactly under which conditions this happens, but to me it seems to be whenever there is one sizer directly inside of another sizer - which is a difficult situation to avoid.

The bug is that wxSmith generates bad code for building the frame. More specifically, it gets confused as to which of the sizers that it is supposed to assign to the frame (top level). The result is that the generated code will try to assign (SetSizer()) more than one sizer to the frame (which is bad), and also to run Layout() more than once on the frame (which is equally bad).

The result is that the application crashes when trying to build the frame in question. This happens in the OptionsFrame in DSMenu.

Solution:

Open dsm-options-frame.cpp and go to the BuildContent method.

Near the end of the generated code

For example - below is the generated code I am talking about as it looks right now (except for that I have truncated some long lines). The Connect calls at the very end has nothing to do with this, I just kept them to show how it looks:

  ...
  panel_frame_->SetSizer(sizer_panel_);
  SetSizer(sizer_panel_);
  Layout();
  sizer_frame_->Add(panel_frame_, 1, wxSHAPED ... wxALIGN_CENTER_VERTICAL, 5);
  SetSizer(sizer_frame_);
  SetSizer(sizer_frame_);
  Layout();
  Center();
  
  Connect(id_button_cancel_,wxEVT_COMMAND_BUTTON_CLICKED, ... );
  Connect(id_button_ok_,wxEVT_COMMAND_BUTTON_CLICKED, ... );
  //*)

The problem is the extra calls to SetSizer and Layout.

Lines like the first one in the example is not a problem. It is a call to SetSizer, but for the wxPanel in panel_frame_. We only care about direct calls to the two methods.

What you need to do is to remove (or comment out) all direct calls to SetSizer and Layout except for the very last one. The result will be something like this:

  ...
  panel_frame_->SetSizer(sizer_panel_);
  // SetSizer(sizer_panel_);
  // Layout();
  sizer_frame_->Add(panel_frame_, 1, wxSHAPED ... wxALIGN_CENTER_VERTICAL, 5);
  // SetSizer(sizer_frame_);
  SetSizer(sizer_frame_);
  Layout();
  Center();
  
  Connect(id_button_cancel_,wxEVT_COMMAND_BUTTON_CLICKED, ... );
  Connect(id_button_ok_,wxEVT_COMMAND_BUTTON_CLICKED, ... );
  //*)

So, the good news is that this is easy to fix. The bad new is that you need to do this every time that you have changed something in dsm-options-frame.wxs.