smorgasbork

 
  • Increase font size
  • Default font size
  • Decrease font size

qooxtunes 1.1

E-mail Print

qooxtunes, the webinterface addon for XBMC, is now in version 1.1. Read on to learn more about the new features.

Read more...
 

A review of "site tour" Javascript libraries

E-mail Print

I recently had a need to display a "site tour" to point out important features to our users. I was happy to discover that there are some very easy-to-use libraries out there for this purpose. However, I found that each library was ultimately lacking at least one vital feature. I wish these authors would put their heads together and build the ultimate site tour.

Until they do, I present my review of a few of the leading candidates

Read more...
 

XBMC: hardware selection

E-mail Print

I recently put together an XBMC system to replace an aging Apple TV (first gen). It was my interest in the Raspberry Pi that brought me to the idea of using XBMC, although ultimately, I did not end up going with a Raspberry Pi based system (I use one for other things around the house, but not the home theater).

I thought I'd share some of the decisions I made so others could benefit.

Here's what I went with:

(all prices in USD, circa late September 2013)

Total cost: $397

I selected the NUC after looking at a lot of options. After reading lots about XBMC on the Raspberry Pi, I got the distinct impression that the experience is tolerable. Not good, but tolerable. I was building this system for my family, so it had to pass the wife test. She'd already seen what was possible with our first-gen Apple TV. I couldn't go backwards in performance. I considered systems like The Little Black Box, but their forums didn't inspire confidence. I thought about Ouya, but at the end of the day, I came back around to a more standard PC platform like the NUC. That seemed a much safer bet.

I wanted small, quiet, and low heat for my system. In the SFF category the NUC was cheaper than a lot of its competitors. If there was one drawback, it was that you have to use an mSATA drive as the primary storage on the device. Being more of a server guy, I wasn't familiar with mSATA, and there was a bit of sticker shock when I saw how much I was going to have to pay. I bought the smallest size available on the market at that time, knowing that my media was going to be stored on an external USB-connected drive.

I put 4GB of RAM in the system, which is probably more than required. I didn't know if 2GB was going to be enough, and I don't even know if you could really get any significant cost savings by going with 2.

I bought the StarTech docking station, which might be overkill, but it's a really nifty box. I can drop a drive in the second bay to run backups. In general, it's just a good device to have around the house.

In the name of quiet and low heat output, I went with a laptop drive at 5400 RPM. I'm mostly using my XBMC for music playback, so the speed of the drive is not critical. And honestly, I think that even this speed is enough for most streaming video applications.

Finally, I went with the Sanoxy remote based on what I'd read in reviews. There are definitely a lot of opinions, good and bad, on this device. But the bottom line for me was that it worked, and it was cheap. I don't even use the remote; I use the USB IR dongle and a Logitech Harmony remote (I set up the device in the Harmony software as "Computer >> Media Center PC >> Chinavasion >> CVSB-983"). I had to do some further tweaking of the key mappings, but it wasn't too bad. I'll write about that in a future installment.

I should mention that I'm using OpenElec to run XBMC. It was ridiculously easy to get the software installed and running. There are some limitations to OpenElec, but so far, nothing I can't live with.

So what do I think of the system? It performs very well. I can stream music, navigate the menus, and run visualizers with no lag at all in the UI (and I consider myself pretty picky when it comes to stuff like that). There are a couple of little things that bug me:

  • Sometimes, I seem to lose the HDMI out; I usually have to reboot the system when that happens. I haven't established a pattern yet, but it might have something to do with coming out of a sleep state
  • No wake on USB: the system goes to sleep almost every night; the only way to wake it back up is to push the power button. I don't want to waste electricity, but it would be nice if I could wake it up via the remote.

All in all, I've been very pleased with the new system, and I'm happy to be out of the walled garden of the AppleTV. I look forward to many years of being able to update the software, install add-ons, and even build my own UI for the system.

 

qooxtunes

E-mail Print

qooxtunes is a webinterface addon for XBMC. It leverages the powerful qooxdoo framework to present the user with a RIA interface that emulates some of the best features of iTunes for library management. It currently only supports music, but it could be extended to handle video and photos.

Read more...
 

Saving and restoring qooxdoo table column sizes, visibility, and order

E-mail Print

I was working on a music library management application built on qooxdoo, and I've always liked the way iTunes manages columns in its tables. You can hide and show columns, rearrange them, and resize them, and iTunes will remember your settings when you launch it next.

With a little work, you can do the same with a qooxdoo table. You just need to gather the column state, serialize it, and write it to a cookie whenever the user changes column state.

        __loading_column_state : false,

        __cookie_name : 'table_column_state',

        save_column_state : function ()
        {
            if (this.__loading_column_state)
            {
                return;
            }

            var tcm = this.getTableColumnModel ();

            var num_cols = tcm.getOverallColumnCount();

            var hidden_cols = [];

            var state = {};
            state.col_widths = [];
            state.col_visible = [];
            for (var i = 0; i < num_cols; i++)
            {
                state.col_visible.push (tcm.isColumnVisible (i) ? 1 : 0);
                state.col_widths.push (tcm.getColumnWidth (i));

                if (!tcm.isColumnVisible (i))
                {
                    hidden_cols.push (i);
                }

            }
            var vis_cols = tcm.getVisibleColumns ();

            state.col_order = [];

            for (var i = 0; i < vis_cols.length; i++)
            {
                state.col_order.push (vis_cols[i]);
            }

            for (var i = 0; i < hidden_cols.length; i++)
            {
                state.col_order.push (hidden_cols[i]);
            }

            var str_state = JSON.stringify (state);

            qx.module.Cookie.set (this.__cookie_name, str_state, 1000);
        }

Then you need to load the state when the table appears:

        load_column_state : function ()
        {
            this.__loading_column_state = true;

            var str_state = qx.module.Cookie.get (this.__cookie_name);

            if (str_state === null)
            {
                this.__loading_column_state = false;
                return;
            }

            var state = JSON.parse (str_state);

            var tcm = this.getTableColumnModel ();
            var num_cols = tcm.getOverallColumnCount();

            // if the application code has changed and the number of columns is different since
            // the time we saved the cookie, just ignore the cookie
            if (state.col_order.length != num_cols)
            {
                return;
            }

            tcm.setColumnsOrder(state.col_order);

            for (var i = 0; i < num_cols; i++)
            {
                tcm.setColumnVisible(i, (state.col_visible[i] == 1));
                tcm.setColumnWidth(i, state.col_widths[i]);
            }

            this.__loading_column_state = false;
        },

You run this code at specific events in the table lifecycle:

        this.addListener ('appear', function (e) {
            this.load_column_state ();

            tcm.addListener ('widthChanged', function (e) {
                this.save_column_state ();
            }, this);
            tcm.addListener ('orderChanged', function (e) {
                this.save_column_state ();
            }, this);
            tcm.addListener ('visibilityChanged', function (e) {
                this.save_column_state ();
            }, this);
        }, this);

Try it in the playground, or see a basic class implementation below:

qx.Class.define("yournamespace.stateful_table",
{
    extend :  qx.ui.table.Table,

    construct : function ()
    {
        var custom =
        {
            tableColumnModel : function(obj) {
                return new qx.ui.table.columnmodel.Basic(obj);
            }
        };

        this.__tm = new qx.ui.table.model.Simple();
        this.__tm.setColumns([ "A", "B", "C"],
                ['a', 'b', 'c']);

        this.base (arguments, this.__tm, custom);

        var tcm = this.getTableColumnModel ();

        this.addListener ('appear', function (e) {
            this.load_column_state ();

            tcm.addListener ('widthChanged', function (e) {
                this.save_column_state ();
            }, this);
            tcm.addListener ('orderChanged', function (e) {
                this.save_column_state ();
            }, this);
            tcm.addListener ('visibilityChanged', function (e) {
                this.save_column_state ();
            }, this);
        }, this);
    },

    members : {

        __loading_column_state : false,

        __cookie_name : 'table_column_state',


        load_column_state : function ()
        {
            this.__loading_column_state = true;

            var str_state = qx.module.Cookie.get (this.__cookie_name);

            if (str_state === null)
            {
                this.__loading_column_state = false;
                return;
            }

            var state = JSON.parse (str_state);

            var tcm = this.getTableColumnModel ();
            var num_cols = tcm.getOverallColumnCount();

            // if the application code has changed and the number of columns is different since
            // the time we saved the cookie, just ignore the cookie
            if (state.col_order.length != num_cols)
            {
                return;
            }

            tcm.setColumnsOrder(state.col_order);

            for (var i = 0; i < num_cols; i++)
            {
                tcm.setColumnVisible(i, (state.col_visible[i] == 1));
                tcm.setColumnWidth(i, state.col_widths[i]);
            }

            this.__loading_column_state = false;
        },

        save_column_state : function ()
        {
            if (this.__loading_column_state)
            {
                return;
            }

            var tcm = this.getTableColumnModel ();

            var num_cols = tcm.getOverallColumnCount();

            var hidden_cols = [];

            var state = {};
            state.col_widths = [];
            state.col_visible = [];
            for (var i = 0; i < num_cols; i++)
            {
                state.col_visible.push (tcm.isColumnVisible (i) ? 1 : 0);
                state.col_widths.push (tcm.getColumnWidth (i));

                if (!tcm.isColumnVisible (i))
                {
                    hidden_cols.push (i);
                }

            }
            var vis_cols = tcm.getVisibleColumns ();

            state.col_order = [];

            for (var i = 0; i < vis_cols.length; i++)
            {
                state.col_order.push (vis_cols[i]);
            }

            for (var i = 0; i < hidden_cols.length; i++)
            {
                state.col_order.push (hidden_cols[i]);
            }

            var str_state = JSON.stringify (state);

            qx.module.Cookie.set (this.__cookie_name, str_state, 1000);
        }
    }
});

 
  • «
  •  Start 
  •  Prev 
  •  1 
  •  2 
  •  3 
  •  4 
  •  5 
  •  6 
  •  7 
  •  8 
  •  9 
  •  10 
  •  Next 
  •  End 
  • »