Checkpoint.
* Got fonts working cross platform by only using SVG. This will change
with the new webkit engine when Tide 1.4 is released.
* Use the Tide API accessors for cross-window data.
* Make the ThingFish server object an observable, so models and their
URI bases can be changed easily.
* Add options for saving window state and position.
* Contextual menu updates/tests for later.
/*######################################################################
### D A S C Y L L U S N A M E S P A C E
######################################################################*/
var D = {
// Versions
platform: Ti.getPlatform(),
name: Ti.API.Application.getName(),
version: Ti.API.Application.getVersion(),
ti_version: Ti.getVersion(),
architecture: Ti.Platform.getArchitecture(),
copyright: Ti.App.getCopyright(),
// Declared windows, for easy referencing
window: {},
// Very first run of Dasycllus?
firstrun: false,
// Persistent storage DB handle.
db: null,
// The primary main tray menu.
menu: null,
// The current TF server we're connected to.
//
// uri: http://localhost:8080/
// version: Thingfish 0.5.0 (build ...)
//
tf: new can.Observe({
uri: null,
version: null
}),
// Actions to perform at startup.
//
init: function() {
var D = this;
D.initDB();
D.window.main = Ti.UI.getMainWindow();
D.initMenu();
// Thingfish server object event handlers.
//
D.tf.bind( 'uri', D.initTF );
var server_uri = D.getPref( 'server_uri' );
if ( server_uri ) D.tf.attr( 'uri', server_uri );
// Main window event handlers.
//
D.window.main.addEventListener( 'moved', function() { D.saveWindowState(); });
D.window.main.addEventListener( 'resized', function() {
D.delay( function() {
D.saveWindowState();
},
500, true );
});
$( document ).contextmenu( function() { D.window.main.setContextMenu( D.menu ); });
// Each Tide window is an environment unto itself. There is a global
// key/val namespace, though. Make the 'D' namespace accessible to other windows here.
Ti.API.set( 'D', D );
},
// Determine if we should be running in developer mode.
// This requires that the user has previously enabled it in preferences,
// and the --debug command line option was passed.
//
devmode: function() {
return Ti.App.getArguments()[0] == "--debug" && this.getBoolPref( 'devmode' );
},
// Event handler for changing the Thingfish server URI.
// Sanity check and instantiate appropriate Model(s).
//
initTF: function( event, uri, old_uri ) {
var serverOk = function( data, status, xhr ) {
var info = xhr.getResponseHeader( 'x-thingfish' );
if ( ! info ) {
D.tf.attr( 'version', null );
D.notify( 'Unable to connect.', 'Not a Thingfish server?' );
return;
}
D.tf.attr( 'version', data.version )
D.notify( 'Connected.', info );
// FIXME: models?
};
try {
$.ajax({
url: uri + '/serverinfo',
async: false,
success: serverOk,
error: function( xhr, status, err ) {
D.tf.attr( 'version', null );
D.notify( 'Unable to connect.', err ? err : 'Network error.' );
},
dataType: 'json'
});
}
catch( err ) {
console.log( err );
};
},
// Open a handle to the local database, initializing it
// if necessary and performing rudamentary upwards migrations.
//
initDB: function() {
this.db = Ti.Database.openFile(
Ti.Filesystem.getFile(
Ti.Filesystem.getApplicationDataDirectory(), 'dascyllus.db'
)
);
var rv = this.db.execute( 'PRAGMA user_version' );
var schema_version = parseInt( rv.fieldByName('user_version') );
var schemas_dir = Ti.Filesystem.getFile(
Ti.Filesystem.getApplicationDirectory(), 'sql' );
var current_version = schemas_dir.getDirectoryListing().length;
if ( schema_version == 0 ) {
this.notify( 'Welcome!', 'Dascyllus defaults installed.' );
this.firstrun = true;
}
if ( schema_version != current_version ) {
console.log( 'Dascyllus DB at version ' + schema_version +
', needs to be upgraded to ' + current_version + '.' );
while ( schema_version != current_version ) {
var update_version = schema_version + 1;
console.log( ' updating to version ' + update_version );
var line;
var schema_file = Ti.Filesystem.getFileStream(
Ti.Filesystem.getApplicationDirectory(), 'sql', update_version + '.sql' );
schema_file.open();
this.db.execute( 'BEGIN' );
while ( line = schema_file.readLine() ) this.db.execute( line.toString() );
this.db.execute( 'PRAGMA user_version = ' + update_version );
this.db.execute( 'COMMIT' );
schema_version = update_version;
schema_file.close();
}
}
return this.db;
},
// Return a preference's current value, or null if nonexistent.
//
getPref: function( pref ) {
var row = this.db.execute( "SELECT val FROM prefs WHERE key=?", pref );
if ( ! row.isValidRow() ) return null;
var value = row.fieldByName( 'val' );
row.close();
return value;
},
// Return a preference's current value as a boolean.
//
getBoolPref: function( pref ) {
return this.getPref( pref ) == '1'
},
// Insert or update a preference value.
//
setPref: function( pref, value ) {
this.db.execute( 'BEGIN' );
// Row already exists, remove it first.
if ( this.getPref( pref ) != null )
this.db.execute( 'DELETE FROM prefs WHERE key=?', pref );
this.db.execute( 'INSERT INTO prefs VALUES ( ?, ? )', pref, value );
this.db.execute( 'COMMIT' );
},
// Set a bool preference value.
//
setBoolPref: function( pref, value ) {
this.setPref( pref, (value ? '1' : '0') );
},
// Generic delayed callback that returns the timer handle.
//
delay: (function() {
var timer = null;
return function( callback, ms, clear_previous ) {
if ( clear_previous) clearTimeout( timer );
timer = setTimeout( callback, ms );
return timer;
};
})(),
// Create and show a new notification.
//
notify: function( title, message, callback ) {
if ( ! callback ) callback = function(){};
var notification = Ti.Notification.createNotification({
'title' : title,
'message' : message,
'timeout' : 10,
'callback' : callback,
});
notification.show();
return notification;
},
// Record the main window position and state.
//
saveWindowState: function() {
var w = this.window.main;
var x = w.getX();
var y = w.getY();
var width = w.getWidth();
var height = w.getHeight();
var hiddenMenu = this.menu.getItemAt( 2 ).getSubmenu().getItemAt( 0 );
var fullScreenMenu = this.menu.getItemAt( 2 ).getSubmenu().getItemAt( 1 );
var pos = this.getPref( 'window_pos' );
pos = Ti.JSON.parse( pos );
// first time
//
if ( ! pos ) {
pos = {
x: x,
y: y,
width: width,
height: height,
fullscreen: false,
hidden: false
};
}
if ( w.isVisible() ) {
pos.hidden = false;
fullScreenMenu.enable();
}
else {
pos.hidden = true;
fullScreenMenu.disable();
}
if ( w.isFullScreen() ) {
pos.fullscreen = true;
hiddenMenu.disable();
}
else {
pos.fullscreen = false;
hiddenMenu.enable();
}
if ( ! pos.fullscreen ) {
pos.x = x;
pos.y = y;
pos.width = width;
pos.height = height;
}
this.setPref( 'window_pos', Ti.JSON.stringify(pos) );
},
// Set the main window according to previously saved values.
//
setWindowState: function() {
var w = this.window.main;
var pos = this.getPref( 'window_pos' );
var hiddenMenu = this.menu.getItemAt( 2 ).getSubmenu().getItemAt( 0 );
var fullScreenMenu = this.menu.getItemAt( 2 ).getSubmenu().getItemAt( 1 );
pos = Ti.JSON.parse( pos );
if ( ! pos ) return;
if ( pos.fullscreen ) {
w.setFullscreen( true );
hiddenMenu.disable();
}
else {
console.log( "Moving to x:" + pos.x + ", y:" + pos.y );
console.log( "Sizing to w:" + pos.width + ", h:" + pos.height );
w.moveTo( pos.x, pos.y );
w.setSize( pos.width, pos.height );
hiddenMenu.enable();
if ( pos.hidden ) {
w.hide();
fullScreenMenu.disable();
}
else {
w.show();
fullScreenMenu.enable();
}
}
this.saveWindowState();
},
// Build the main menu.
//
initMenu: function() {
this.menu = Ti.UI.createMenu();
var tray = Ti.UI.addTray( 'app://img/tray_icon.png' );
// windows
// ---------------------------------------------------------------
this.menu.addItem( 'About', function() {
if ( D.window.about && D.window.about.isVisible() ) {
D.window.about.focus();
}
else {
D.window.about = D.window.main.createWindow( 'app://window/about.html' );
D.window.about.setTopMost( true );
D.window.about.open();
}
});
this.menu.addItem( 'Preferences', function() {
if ( D.window.prefs && D.window.prefs.isVisible() ) {
D.window.prefs.focus();
}
else {
D.window.prefs = D.window.main.createWindow( 'app://window/prefs.html' );
D.window.prefs.open();
}
});
// view menu
// ---------------------------------------------------------------
var viewMenu = Ti.UI.createMenuItem( 'View' );
viewMenu.addItem( 'Toggle Hidden', function() {
if ( D.window.main.isVisible() ) {
D.window.main.hide();
}
else {
D.window.main.show();
}
D.saveWindowState();
});
viewMenu.addItem( 'Toggle Full Screen', function() {
D.window.main.setFullscreen( ! D.window.main.isFullscreen() );
D.saveWindowState();
});
this.menu.appendItem( viewMenu );
// other
// --------------------------------------------------------------
if ( this.getBoolPref('devmode') ) {
this.menu.addItem( 'Restart', function() {
Ti.App.restart();
});
}
tray.setMenu( this.menu );
Ti.UI.setDockMenu( this.menu );
// D.window.main.setContextMenu( this.menu );
// Ti.UI.setMenu( menu );
},
/*######################################################################
### M O D E L S
######################################################################*/
// // A Thingfish asset.
// //
// initModel: function() {
// D.Asset = can.Model({
// id : 'oid',
// findAll: 'GET ' + D.tf.uri + '/',
// findOne: 'GET ' + D.tf.uri + '/{oid}',
// create : 'POST ' + D.tf.uri + '/',
// update : 'PUT ' + D.tf.uri + '/{oid}',
// destroy: 'DELETE ' + D.tf.uri + '/{oid}',
// // TODO: other metadata hooks? diff model?
// metadata: 'GET ' + D.tf.uri + '/{oid}/metadata',
// }, {}).
// bind( 'created', function( ev, asset ) {
// console.debug( "Created a new asset " + asset.id );
// }).
// bind( 'updated', function( ev, asset ) {
// console.debug( "Updated asset " + asset.id );
// }).
// bind( 'destroyed', function( ev, asset ) {
// console.debug( "Removed asset " + asset.id );
// }
// );
// },
};