[dasher: 7/11] iPhone GUI: actions menu from toolbar, move tilt settings to make space



commit f25b033f60703a8fdb794e6e49d0ded1d4a8e609
Author: Alan Lawrence <acl33 inf phy cam ac uk>
Date:   Tue May 25 21:02:53 2010 +0100

    iPhone GUI: actions menu from toolbar, move tilt settings to make space
    
    Per-action icon if only 1 enabled; else small settings tab-icon => UIActionSheet
    Inc paste command - at insertion point, followed by rebuilding model.
    Move copy-to-clipboard functionality into DasherAppDelegate
    (Tilt Calibration accessed by button at bottom of InputMethod Selector)

 Src/iPhone/Classes/Actions.h                 |   18 ++
 Src/iPhone/Classes/Actions.mm                |  370 ++++++++++++++++++++++++++
 Src/iPhone/Classes/CDasherInterfaceBridge.mm |    3 +-
 Src/iPhone/Classes/CalibrationController.h   |    2 +-
 Src/iPhone/Classes/CalibrationController.mm  |    7 +-
 Src/iPhone/Classes/DasherAppDelegate.h       |    8 +-
 Src/iPhone/Classes/DasherAppDelegate.mm      |   42 ++--
 Src/iPhone/Classes/InputMethodSelector.h     |    1 +
 Src/iPhone/Classes/InputMethodSelector.mm    |   29 ++-
 Src/iPhone/Dasher.xcodeproj/project.pbxproj  |   34 +++
 Src/iPhone/bubble.png                        |  Bin 0 -> 1060 bytes
 Src/iPhone/bubbletrash.png                   |  Bin 0 -> 863 bytes
 Src/iPhone/copy.png                          |  Bin 0 -> 484 bytes
 Src/iPhone/paste.png                         |  Bin 0 -> 442 bytes
 Src/iPhone/scissors.png                      |  Bin 0 -> 790 bytes
 Src/iPhone/spanner.png                       |  Bin 0 -> 632 bytes
 Src/iPhone/spanner_lg.png                    |  Bin 0 -> 529 bytes
 17 files changed, 483 insertions(+), 31 deletions(-)
---
diff --git a/Src/iPhone/Classes/Actions.h b/Src/iPhone/Classes/Actions.h
new file mode 100644
index 0000000..b48a96d
--- /dev/null
+++ b/Src/iPhone/Classes/Actions.h
@@ -0,0 +1,18 @@
+//
+//  U.h
+//  Dasher
+//
+//  Created by Alan Lawrence on 26/05/2010.
+//  Copyright 2010 Cavendish Laboratory. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+ interface ActionButton : UIBarButtonItem <UIActionSheetDelegate> {
+  UIToolbar *toolbar;
+  int numActionsOn, *actionsOn;
+}
+- (UIViewController *)tabConfigurator;
+- (id)initForToolbar:(UIToolbar *)toolbar;
+
+ end
diff --git a/Src/iPhone/Classes/Actions.mm b/Src/iPhone/Classes/Actions.mm
new file mode 100644
index 0000000..1f9c61d
--- /dev/null
+++ b/Src/iPhone/Classes/Actions.mm
@@ -0,0 +1,370 @@
+//
+//  U.m
+//  Dasher
+//
+//  Created by Alan Lawrence on 26/05/2010.
+//  Copyright 2010 Cavendish Laboratory. All rights reserved.
+//
+
+#import "Actions.h"
+#import "DasherAppDelegate.h"
+
+ interface ActionButton ()
+- (void)performAction:(int)which checkClear:(BOOL)bCheck;
+- (void)scan;
+- (UIViewController *)navConfigurator;
+ end;
+
+ interface ActionConfigurator : UITableViewController {
+  ActionButton *button;
+}
+-(id)initWithButton:(ActionButton *)_button;
+ end
+
+typedef struct {
+  NSString *dispName;
+  NSString *settingName;
+  NSString *toolbarIconFile;
+} SAction;
+
+static SAction actions[] = {
+  {@"Email",@"iphone_act_email", @"mail.png"},
+  {@"Speak",@"iphone_act_speak", @"bubble.png"},
+  {@"Speak and Clear",@"iphone_act_speak_clear", @"bubbletrash.png"},
+  {@"Copy to Clipboard",@"iphone_act_copy", @"copy.png"},
+  {@"Cut to Clipboard", @"iphone_act_cut", @"scissors.png"},
+  {@"Paste from Clipboard", @"iphone_act_paste", @"paste.png"},
+};
+
+static const int numActions = sizeof(actions) / sizeof(actions[0]);
+
+static NSString *actionIconFile = @"spanner.png";
+
+ implementation ActionButton
+
+-(id)initForToolbar:(UIToolbar *)_toolbar {
+  if (self = [super initWithImage:[UIImage imageNamed:actionIconFile] style:UIBarButtonItemStylePlain target:self action:@selector(clicked)]) {
+    toolbar = _toolbar;
+    actionsOn = new int[numActions];
+    [self scan];
+  }
+  return self;
+}
+
+-(UIViewController *)tabConfigurator {
+  ActionConfigurator *conf=[[[ActionConfigurator alloc] initWithButton:self] autorelease];
+  //for a tab in the settings tabcontroller...
+  conf.tabBarItem.title=@"Actions";
+  conf.tabBarItem.image=[UIImage imageNamed:@"spanner_lg.png"];
+  return conf;
+}
+
+-(UIViewController *)navConfigurator {
+  ActionConfigurator *conf = [[[ActionConfigurator alloc] initWithButton:self] autorelease];
+  //for root of a navigationcontroller...
+  conf.navigationItem.title=@"Configure Actions";
+  conf.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(settingsDone)] autorelease];
+  return conf;
+}
+
+-(void)dealloc {
+  delete[] actionsOn;
+  [super dealloc];
+}
+
+-(void)scan {
+  numActionsOn=0;
+  NSUserDefaults *settings=[NSUserDefaults standardUserDefaults];
+  NSString *iconFile=nil;
+  for (int i=0; i<numActions; i++) {
+    if ([settings boolForKey:actions[i].settingName]) {
+      actionsOn[numActionsOn++]=i;
+      iconFile = actions[i].toolbarIconFile;
+    }
+  }
+  //just to be safe, fill to end of actionsOn array with -1s:
+  for (int i=numActionsOn; i<numActions; i++) actionsOn[i]=-1;
+  
+  //multiple items, _or_ none (=>configure), display generic actions/tools icon
+  if (numActionsOn!=1) iconFile = actionIconFile;
+  [self setImage:[UIImage imageNamed:iconFile]];
+}
+
+- (void)clicked {
+  if (numActionsOn==0) {
+    //no actions enabled! display configurator...
+    [[DasherAppDelegate theApp] presentModalViewController:[[[UINavigationController alloc] initWithRootViewController:[self navConfigurator]] autorelease] animated:YES];
+  } else if (numActionsOn==1) {
+    //a single action is enabled...
+    [self performAction:actionsOn[0] checkClear:YES];
+  } else {
+    //multiple actions enabled; display a menu to choose...
+    
+    //we display the choice to the user as a UIActionSheet, with one button per enabled action plus 'cancel'.
+    // Unfortunately, to get the 'cancel' button to display properly, the API requires we pass in all the button titles
+    // to the constructor as a varargs array - so the number of args is statically hard-coded as part of the call site
+    // (there is no portable/guaranteed way to pass in a standard NSArray or NSString **. There may be implementation-dependent
+    // hacks that happen to work atm but these are not part of the API/spec and hence not to be relied upon!!)
+    UIActionSheet *choice;
+    switch (numActionsOn) {
+      case 2:
+        choice = [[[UIActionSheet alloc] initWithTitle:@"Actions" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:actions[actionsOn[0]].dispName,actions[actionsOn[1]].dispName,nil] autorelease];
+        break;
+      case 3:
+        choice = [[[UIActionSheet alloc] initWithTitle:@"Actions" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:actions[actionsOn[0]].dispName,actions[actionsOn[1]].dispName,actions[actionsOn[2]].dispName,nil] autorelease];
+        break;
+      case 4:
+        choice = [[[UIActionSheet alloc] initWithTitle:@"Actions" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:actions[actionsOn[0]].dispName,actions[actionsOn[1]].dispName,actions[actionsOn[2]].dispName,actions[actionsOn[3]].dispName,nil] autorelease];
+        break;
+      case 5:
+        choice = [[[UIActionSheet alloc] initWithTitle:@"Actions" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:actions[actionsOn[0]].dispName,actions[actionsOn[1]].dispName,actions[actionsOn[2]].dispName,actions[actionsOn[3]].dispName,actions[actionsOn[4]].dispName,nil] autorelease];
+        break;
+      case 6:
+        choice = [[[UIActionSheet alloc] initWithTitle:@"Actions" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:actions[actionsOn[0]].dispName,actions[actionsOn[1]].dispName,actions[actionsOn[2]].dispName,actions[actionsOn[3]].dispName,actions[actionsOn[4]].dispName,actions[actionsOn[5]].dispName,nil] autorelease];
+        break;
+      default:
+        //ok, some other number! But implementing for future-proofing...
+        // We don't use this method normally because the cancel button will appear in the wrong place (at the top!),
+        // but it'll do as a fallback...
+        choice = [[[UIActionSheet alloc] initWithTitle:@"Actions" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:nil] autorelease];
+        for (int i = 0; i<numActionsOn; i++)
+          [choice addButtonWithTitle:actions[actionsOn[i]].dispName];
+        break;
+      //case 0, case 1: handled above
+    }
+    [choice showFromToolbar:toolbar];
+  }
+}
+
+-(void)settingsDone {
+  [[DasherAppDelegate theApp] dismissModalViewControllerAnimated:YES];
+}
+
+- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
+  if (buttonIndex == [actionSheet cancelButtonIndex]) return;
+  
+  //buttons should all be before cancel, but if we used fallback case above, they might be after; be defensive...
+  if (buttonIndex > [actionSheet cancelButtonIndex]) buttonIndex--;
+  buttonIndex -= [actionSheet firstOtherButtonIndex];
+
+  //the actionsOn array should have been setup when the UIActionSheet was posted...
+  int which = actionsOn[buttonIndex];
+  DASHER_ASSERT(which!=-1);
+  if (which!=-1) [self performAction:which checkClear:NO];
+}
+
+- (void)performAction:(int)which checkClear:(BOOL)bCheck {
+  DasherAppDelegate *deleg = [DasherAppDelegate theApp];
+  NSString *theText = [deleg allText];
+  switch (which) {
+    case 0: { //email
+      NSString *mailString = [NSString stringWithFormat:@"mailto:?body=%@";, 
+                              [theText stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]];
+      [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mailString]];      
+      return; //tho we won't actually get even here...
+    }
+    case 1: //speak
+    case 2:
+      [deleg speak:theText interrupt:YES];
+      if (which==1) return;
+      //continue after switch to clear
+      break;
+    case 3:
+    case 4:
+      [deleg copy:theText];
+      if (which==3) return;
+      //continue after switch to clear
+      break;
+    case 5:
+      [deleg insertText:[UIPasteboard generalPasteboard].string];
+      return;
+  }
+  //clear...
+  if (bCheck) [deleg clearBtn]; else [deleg clearText];
+}
+
+ end
+
+ implementation ActionConfigurator
+
+- (id)initWithButton:(ActionButton *)_button {
+  if (self = [super initWithStyle:UITableViewStylePlain]) {
+    button = _button;
+  }
+  return self;
+}
+
+#pragma mark -
+#pragma mark View lifecycle
+
+/*
+- (void)viewDidLoad {
+    [super viewDidLoad];
+
+    // Uncomment the following line to preserve selection between presentations.
+    self.clearsSelectionOnViewWillAppear = NO;
+ 
+    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
+    // self.navigationItem.rightBarButtonItem = self.editButtonItem;
+}
+*/
+
+/*
+- (void)viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+}
+*/
+/*
+- (void)viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+}
+*/
+/*
+- (void)viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+}
+*/
+/*
+- (void)viewDidDisappear:(BOOL)animated {
+    [super viewDidDisappear:animated];
+}
+*/
+/*
+// Override to allow orientations other than the default portrait orientation.
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
+    // Return YES for supported orientations
+    return (interfaceOrientation == UIInterfaceOrientationPortrait);
+}
+*/
+
+
+#pragma mark -
+#pragma mark Table view data source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+    // Return the number of sections.
+    return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    // Return the number of rows in the section.
+  return numActions;
+}
+
+
+// Customize the appearance of table view cells.
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    
+    static NSString *CellIdentifier = @"Cell";
+    
+    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+    UISwitch *sw;
+  
+    if (cell == nil) {
+      cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
+      sw = [[[UISwitch alloc] initWithFrame:CGRectMake(210.0,5.0,100.0,20.0)] autorelease];
+      [sw addTarget:self action:@selector(slid:) forControlEvents:UIControlEventValueChanged];
+      sw.tag=1; 
+      [cell addSubview:sw];
+    } else {
+      DASHER_ASSERT([[cell viewWithTag:1] isKindOfClass:[UISwitch class]]);
+      sw = (UISwitch *)[cell viewWithTag:1];
+    }
+
+    
+    // Configure the cell...
+  SAction *act = &actions[ [indexPath row] ];
+  cell.text = act->dispName;
+  cell.tag = [indexPath row];
+  sw.on = [[NSUserDefaults standardUserDefaults] boolForKey:act->settingName];
+  return cell;
+}
+
+- (void)slid:(id)sender {
+  DASHER_ASSERT([sender isKindOfClass:[UISwitch class]]);
+  UISwitch *sw = (UISwitch *)sender;
+  DASHER_ASSERT([[sw superview] isKindOfClass:[UITableViewCell class]]);
+  SAction *act = &actions[ [sw superview].tag ];
+  [[NSUserDefaults standardUserDefaults] setBool:sw.on forKey:act->settingName];
+  [button scan];
+}
+
+/*
+// Override to support conditional editing of the table view.
+- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
+    // Return NO if you do not want the specified item to be editable.
+    return YES;
+}
+*/
+
+
+/*
+// Override to support editing the table view.
+- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
+    
+    if (editingStyle == UITableViewCellEditingStyleDelete) {
+        // Delete the row from the data source
+        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
+    }   
+    else if (editingStyle == UITableViewCellEditingStyleInsert) {
+        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
+    }   
+}
+*/
+
+
+/*
+// Override to support rearranging the table view.
+- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
+}
+*/
+
+
+/*
+// Override to support conditional rearranging of the table view.
+- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
+    // Return NO if you do not want the item to be re-orderable.
+    return YES;
+}
+*/
+
+
+#pragma mark -
+#pragma mark Table view delegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+  [tableView deselectRowAtIndexPath:indexPath animated:NO];
+    // Navigation logic may go here. Create and push another view controller.
+	/*
+	 <#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:@"<#Nib name#>" bundle:nil];
+     // ...
+     // Pass the selected object to the new view controller.
+	 [self.navigationController pushViewController:detailViewController animated:YES];
+	 [detailViewController release];
+	 */
+}
+
+
+#pragma mark -
+#pragma mark Memory management
+
+- (void)didReceiveMemoryWarning {
+    // Releases the view if it doesn't have a superview.
+    [super didReceiveMemoryWarning];
+    
+    // Relinquish ownership any cached data, images, etc that aren't in use.
+}
+
+- (void)viewDidUnload {
+    // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
+    // For example: self.myOutlet = nil;
+}
+
+
+- (void)dealloc {
+  [super dealloc];
+}
+
+
+ end
+
diff --git a/Src/iPhone/Classes/CDasherInterfaceBridge.mm b/Src/iPhone/Classes/CDasherInterfaceBridge.mm
index 37e5423..bc7ac43 100644
--- a/Src/iPhone/Classes/CDasherInterfaceBridge.mm
+++ b/Src/iPhone/Classes/CDasherInterfaceBridge.mm
@@ -266,8 +266,7 @@ void CDasherInterfaceBridge::ExternalEventHandler(Dasher::CEvent *pEvent) {
 }
 
 void CDasherInterfaceBridge::CopyToClipboard(const std::string &strText) {
-  CDasherInterfaceBase::CopyToClipboard(strText);
-  [UIPasteboard generalPasteboard].string=NSStringFromStdString(strText);
+  [dasherApp copy:NSStringFromStdString(strText)];
 }
 
 bool CDasherInterfaceBridge::SupportsSpeech() {
diff --git a/Src/iPhone/Classes/CalibrationController.h b/Src/iPhone/Classes/CalibrationController.h
index b06a97e..54e137d 100644
--- a/Src/iPhone/Classes/CalibrationController.h
+++ b/Src/iPhone/Classes/CalibrationController.h
@@ -37,7 +37,7 @@ enum settableParam {
   float minY, maxY, minX, maxX; //if SETTING_VERT, the min/max values seen so far (also displayed immediately in vertMin, vertMax, vertX)
 }
 
--(id)initWithTabCon:(UITabBarController *)tabCon;
+-(id)init;
 +(void)doSetup;
 
 @end
diff --git a/Src/iPhone/Classes/CalibrationController.mm b/Src/iPhone/Classes/CalibrationController.mm
index 836cbde..8f737f8 100644
--- a/Src/iPhone/Classes/CalibrationController.mm
+++ b/Src/iPhone/Classes/CalibrationController.mm
@@ -105,11 +105,10 @@ void setVerticalTiltAxes(float minY, float maxY, float minX, float maxX, BOOL in
 	}
 }
 
-- (id)initWithTabCon:(UITabBarController *)_tabCon {
+- (id)init {
   if (self = [super initWithStyle:UITableViewStyleGrouped]) {
     self.tabBarItem.title = @"Tilting";
-    tabCon = _tabCon;
-	self.tabBarItem.image = [UIImage imageNamed:@"tilt.png"];
+    self.tabBarItem.image = [UIImage imageNamed:@"tilt.png"];
   }
   return self;
 }
@@ -273,8 +272,6 @@ void setVerticalTiltAxes(float minY, float maxY, float minX, float maxX, BOOL in
 	[act showInView:settingLabel];
   }
 //	[act addSubview:settingLabel];
-  else if ([UITabBarController instancesRespondToSelector:@selector(tabBar)])
-    [act showFromTabBar:[tabCon tabBar]];//iPhone OS 3 only...
   else [act showInView:self.view];
   [UIAccelerometer sharedAccelerometer].delegate = self;
 }
diff --git a/Src/iPhone/Classes/DasherAppDelegate.h b/Src/iPhone/Classes/DasherAppDelegate.h
index e06b9d7..b53e3d5 100644
--- a/Src/iPhone/Classes/DasherAppDelegate.h
+++ b/Src/iPhone/Classes/DasherAppDelegate.h
@@ -10,6 +10,7 @@
 #import "CDasherInterfaceBridge.h"
 #import "CDasherScreenBridge.h"
 #import "TextView.h"
+#import "Actions.h"
 
 typedef enum {
   EDIT_CHAR,
@@ -36,7 +37,7 @@ typedef enum {
   BOOL m_bLandscapeSupported;
   /// Should really be part of UIViewController (lockable), below...but then, how to find?
   UILabel *screenLockLabel;
-  
+  ActionButton *actions;
   NSString *m_wordBoundary, *m_sentenceBoundary, *m_lineBoundary;
 }
 
@@ -49,6 +50,11 @@ typedef enum {
 - (void)del:(EEditDistance)amt forwards:(BOOL)bForwards;
 - (BOOL)supportsSpeech;
 - (void)speak:(NSString *)text interrupt:(BOOL)bInt;
+- (void)copy:(NSString *)text;
+//forcibly inserts text - and then rebuilds the model
+- (void)insertText:(NSString *)text;
+- (void)clearBtn; //prompts for confirmation, then calls:
+- (void)clearText;
 - (NSString *)allText;
 - (void)notifySpeedChange;
 - (NSString *)textAtOffset:(unsigned int)offset Length:(unsigned int)length;
diff --git a/Src/iPhone/Classes/DasherAppDelegate.mm b/Src/iPhone/Classes/DasherAppDelegate.mm
index 40d448f..affd253 100644
--- a/Src/iPhone/Classes/DasherAppDelegate.mm
+++ b/Src/iPhone/Classes/DasherAppDelegate.mm
@@ -181,9 +181,10 @@
 	speedBtn = [UIButton buttonWithType:UIButtonTypeCustom];
 	[speedBtn setImageEdgeInsets:UIEdgeInsetsMake(0.0, 2.0, 0.0, 2.0)];
 	[speedBtn addTarget:self action:@selector(fadeSlider) forControlEvents:UIControlEventAllTouchEvents];
-
-	UIBarButtonItem *clear = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(clear)] autorelease];
-	UIBarButtonItem *mail = [[[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"mail.png"] style:UIBarButtonItemStylePlain target:self action:@selector(mail)] autorelease];
+  
+  actions = [[[ActionButton alloc] initForToolbar:tools] autorelease];
+  
+	UIBarButtonItem *clear = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(clearBtn)] autorelease];
 	[tools setItems:[NSArray arrayWithObjects:
 				settings,
 				[[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil] autorelease],
@@ -191,7 +192,7 @@
 				[[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil] autorelease],
 				clear,
 				[[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil] autorelease],
-				mail,
+				actions,
 				nil]];
 	
 	[self.view addSubview:glView];
@@ -267,25 +268,19 @@
 	//[self notifySpeedChange];//no need, CDasherInterfaceBridge calls if SetLongParameter did anything
 }
 
-- (void)clear {
+- (void)clearBtn {
 	UIActionSheet *confirm = [[[UIActionSheet alloc] initWithTitle:@"Start New Document" delegate:self cancelButtonTitle:@"Keep Existing" destructiveButtonTitle:@"Discard Existing" otherButtonTitles:nil] autorelease];
 	[confirm showFromToolbar:tools];
 }
-	
-- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
-	if (buttonIndex == actionSheet.destructiveButtonIndex)
-	{
-		text.text=@"";
-		selectedText.location = selectedText.length = 0;
-		self.dasherInterface->SetBuffer(0);
-	}
-	//...and dismiss?
+
+- (void)clearText {
+  text.text=@"";
+  selectedText.location = selectedText.length = 0;
+  _dasherInterface->SetBuffer(0);
 }
 	
-- (void)mail {
-  NSString *mailString = [NSString stringWithFormat:@"mailto:?body=%@";, 
-						  [text.text stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]];
-  [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mailString]];
+- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
+	if (buttonIndex == actionSheet.destructiveButtonIndex) [self clearText];
 }
 	
 - (void)settings {
@@ -300,10 +295,10 @@
 
     tabs.viewControllers = [NSArray arrayWithObjects:
 							[[[InputMethodSelector alloc] init] autorelease],
-						    [[[CalibrationController alloc] initWithTabCon:tabs] autorelease],
 						    [[[LanguagesController alloc] init] autorelease],
                 [[[StringParamController alloc] initWithTitle:@"Colour" image:[UIImage imageNamed:@"palette.png"] settingParam:SP_COLOUR_ID] autorelease],
 						    [[[MiscSettings alloc] init] autorelease],
+                [actions tabConfigurator],
 						    nil];
   [self presentModalViewController:settings animated:YES];
 }
@@ -353,6 +348,15 @@
   [fliteEng speakText:sText];
 }
 
+- (void) copy:(NSString *)sText {
+  [UIPasteboard generalPasteboard].string=sText;
+}
+
+- (void)insertText:(NSString *)sText {
+  [self outputCallback:sText];
+  _dasherInterface->SetOffset(selectedText.location);
+}
+
 - (void)applicationWillResignActive:(UIApplication *)application {
 	[glView stopAnimation];//.animationInterval = 1.0 / 5.0;
 }
diff --git a/Src/iPhone/Classes/InputMethodSelector.h b/Src/iPhone/Classes/InputMethodSelector.h
index 6242599..775be68 100644
--- a/Src/iPhone/Classes/InputMethodSelector.h
+++ b/Src/iPhone/Classes/InputMethodSelector.h
@@ -11,6 +11,7 @@
 
 @interface InputMethodSelector : UITableViewController {
 	NSIndexPath *selectedPath;
+  UIView *calibButton;
 }
 
 - (id)init;
diff --git a/Src/iPhone/Classes/InputMethodSelector.mm b/Src/iPhone/Classes/InputMethodSelector.mm
index a3128f2..4a87746 100644
--- a/Src/iPhone/Classes/InputMethodSelector.mm
+++ b/Src/iPhone/Classes/InputMethodSelector.mm
@@ -54,9 +54,11 @@ SSectionDesc allMeths[] = {
 {@"Touch Control", asTouchFilters, sizeof(asTouchFilters) / sizeof(asTouchFilters[0])},
 {@"Button Modes", asDynamicFilters, sizeof(asDynamicFilters) / sizeof(asDynamicFilters[0])},
 {@"Tilt Control", asTiltFilters, sizeof(asTiltFilters) / sizeof(asTiltFilters[0])},
-{@"Touch/tilt Combo", asMixedFilters, sizeof(asMixedFilters) / sizeof(asMixedFilters[0])},
+{@"Combined Touch & Tilt", asMixedFilters, sizeof(asMixedFilters) / sizeof(asMixedFilters[0])},
 };
 
+int numSections = sizeof(allMeths) / sizeof(allMeths[0]);
+
 @interface InputMethodSelector ()
 @property (retain) NSIndexPath *selectedPath;
 - (void)doSelect;
@@ -120,13 +122,13 @@ SSectionDesc allMeths[] = {
 #pragma mark Table view methods
 
 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
-    return sizeof(allMeths) / sizeof(allMeths[0]);
+  return numSections + 1; //add 1 for calibrate button
 }
 
 
 // Customize the number of rows in the table view.
 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    return allMeths[section].numFilters;
+  return (section==numSections) ? 0 : allMeths[section].numFilters;
 }
 
 // Customize the appearance of table view cells.
@@ -191,6 +193,22 @@ SSectionDesc allMeths[] = {
   }
 }
 
+- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
+  if (section < numSections) return nil; //no special view => default, using title method (below)
+  if (!calibButton) {
+    calibButton = [[UIView alloc] initWithFrame:CGRectZero];
+    calibButton.backgroundColor = [UIColor clearColor];
+    calibButton.opaque = NO;
+    UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
+    [btn setTitle:@"Calibrate Tilting..." forState:UIControlStateNormal];
+    btn.font = [UIFont boldSystemFontOfSize:18.0];
+    btn.frame = CGRectMake(9.0,2.0,302.0,[self tableView:tableView heightForHeaderInSection:section]-4.0);
+    [btn addTarget:self action:@selector(calibrate) forControlEvents:UIControlEventTouchUpInside];
+    [calibButton addSubview:btn];
+  }
+  return calibButton;
+}
+
 - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
     return allMeths[section].displayName;
 }
@@ -199,6 +217,11 @@ SSectionDesc allMeths[] = {
 	return 40.0f;
 }
 
+- (void)calibrate {
+  CalibrationController *calCon = [[[CalibrationController alloc] init] autorelease];
+  [self.navigationController pushViewController:calCon animated:YES];
+}
+
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 	//[tableView deselectRowAtIndexPath:indexPath animated:NO];
 	self.selectedPath = indexPath;
diff --git a/Src/iPhone/Dasher.xcodeproj/project.pbxproj b/Src/iPhone/Dasher.xcodeproj/project.pbxproj
index 42e5bf9..ac1e934 100755
--- a/Src/iPhone/Dasher.xcodeproj/project.pbxproj
+++ b/Src/iPhone/Dasher.xcodeproj/project.pbxproj
@@ -14,6 +14,12 @@
 		28FD14FE0DC6FC130079059D /* EAGLView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 28FD14FD0DC6FC130079059D /* EAGLView.mm */; };
 		28FD15000DC6FC520079059D /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28FD14FF0DC6FC520079059D /* OpenGLES.framework */; };
 		28FD15080DC6FC5B0079059D /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28FD15070DC6FC5B0079059D /* QuartzCore.framework */; };
+		3302672811B7F0D000C07880 /* copy.png in Resources */ = {isa = PBXBuildFile; fileRef = 3302672611B7F0D000C07880 /* copy.png */; };
+		3302675611B7F80800C07880 /* bubble.png in Resources */ = {isa = PBXBuildFile; fileRef = 3302675411B7F80800C07880 /* bubble.png */; };
+		3302675711B7F80800C07880 /* bubbletrash.png in Resources */ = {isa = PBXBuildFile; fileRef = 3302675511B7F80800C07880 /* bubbletrash.png */; };
+		3302678111B8026900C07880 /* scissors.png in Resources */ = {isa = PBXBuildFile; fileRef = 3302678011B8026900C07880 /* scissors.png */; };
+		3302678C11B8040D00C07880 /* spanner_lg.png in Resources */ = {isa = PBXBuildFile; fileRef = 3302678A11B8040D00C07880 /* spanner_lg.png */; };
+		3302678D11B8040D00C07880 /* spanner.png in Resources */ = {isa = PBXBuildFile; fileRef = 3302678B11B8040D00C07880 /* spanner.png */; };
 		331C73C30F71750D004492FF /* COSXSettingsStore.mm in Sources */ = {isa = PBXBuildFile; fileRef = 331C73C20F71750D004492FF /* COSXSettingsStore.mm */; };
 		331C73F50F717594004492FF /* DasherUtil.mm in Sources */ = {isa = PBXBuildFile; fileRef = 331C73F40F717594004492FF /* DasherUtil.mm */; };
 		331F29430F7A9C270044EB9C /* alphabet-nest.xsl in Resources */ = {isa = PBXBuildFile; fileRef = 331F28B70F7A9C270044EB9C /* alphabet-nest.xsl */; };
@@ -221,6 +227,7 @@
 		3344FE660F71717C00506EAA /* XMLUtil.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3344FE130F71717C00506EAA /* XMLUtil.cpp */; };
 		334AC9B8102AE19400CE6871 /* Locked.png in Resources */ = {isa = PBXBuildFile; fileRef = 334AC9B7102AE19400CE6871 /* Locked.png */; };
 		334B1BF111232A8E007A6DFF /* ParametersController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 334B1BF011232A8E007A6DFF /* ParametersController.mm */; };
+		3354AF4811ADBAFD006CF570 /* Actions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3354AF4711ADBAFD006CF570 /* Actions.mm */; };
 		33627FBA0F7A82CF000C8818 /* training_albanian_SQ.txt in Resources */ = {isa = PBXBuildFile; fileRef = 33627F9A0F7A82CE000C8818 /* training_albanian_SQ.txt */; };
 		33627FBD0F7A82CF000C8818 /* training_bengali_BD.txt in Resources */ = {isa = PBXBuildFile; fileRef = 33627F9D0F7A82CE000C8818 /* training_bengali_BD.txt */; };
 		33627FBF0F7A82CF000C8818 /* training_canna_JP.txt in Resources */ = {isa = PBXBuildFile; fileRef = 33627F9F0F7A82CE000C8818 /* training_canna_JP.txt */; };
@@ -270,6 +277,7 @@
 		33CBB4F2101FA33E00510BF9 /* alphabet.portuguese.xml in Resources */ = {isa = PBXBuildFile; fileRef = 33CBB4EA101FA33E00510BF9 /* alphabet.portuguese.xml */; };
 		33CBB4F3101FA33E00510BF9 /* alphabet.swedish.xml in Resources */ = {isa = PBXBuildFile; fileRef = 33CBB4EB101FA33E00510BF9 /* alphabet.swedish.xml */; };
 		33CBB4F5101FA35900510BF9 /* training_basque_ES.txt in Resources */ = {isa = PBXBuildFile; fileRef = 33CBB4F4101FA35900510BF9 /* training_basque_ES.txt */; };
+		33DA5E2D11B70FA100011CD2 /* paste.png in Resources */ = {isa = PBXBuildFile; fileRef = 33DA5E2C11B70FA100011CD2 /* paste.png */; };
 		33EB483D0F7287DC0048E7C2 /* CDasherScreenBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33EB483C0F7287DC0048E7C2 /* CDasherScreenBridge.mm */; };
 		33EB49240F73E8B30048E7C2 /* AlphabetLetter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 33EB49230F73E8B30048E7C2 /* AlphabetLetter.mm */; };
 		33EC5DF70FF3E30D00275986 /* ButtonMultiPress.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33EC5DF20FF3E30D00275986 /* ButtonMultiPress.cpp */; };
@@ -294,6 +302,12 @@
 		28FD15070DC6FC5B0079059D /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
 		29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
 		32CA4F630368D1EE00C91783 /* Dasher_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Dasher_Prefix.pch; sourceTree = "<group>"; };
+		3302672611B7F0D000C07880 /* copy.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = copy.png; sourceTree = "<group>"; };
+		3302675411B7F80800C07880 /* bubble.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bubble.png; sourceTree = "<group>"; };
+		3302675511B7F80800C07880 /* bubbletrash.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bubbletrash.png; sourceTree = "<group>"; };
+		3302678011B8026900C07880 /* scissors.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = scissors.png; sourceTree = "<group>"; };
+		3302678A11B8040D00C07880 /* spanner_lg.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = spanner_lg.png; sourceTree = "<group>"; };
+		3302678B11B8040D00C07880 /* spanner.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = spanner.png; sourceTree = "<group>"; };
 		3318BEF510F6346F00D4F877 /* DelayedDraw.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DelayedDraw.h; sourceTree = "<group>"; };
 		331C73C10F71750D004492FF /* COSXSettingsStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = COSXSettingsStore.h; sourceTree = "<group>"; };
 		331C73C20F71750D004492FF /* COSXSettingsStore.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = COSXSettingsStore.mm; sourceTree = "<group>"; };
@@ -665,6 +679,8 @@
 		334B1BEF11232A8E007A6DFF /* ParametersController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ParametersController.h; sourceTree = "<group>"; };
 		334B1BF011232A8E007A6DFF /* ParametersController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ParametersController.mm; sourceTree = "<group>"; };
 		334B1C2011233B8B007A6DFF /* ModuleSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModuleSettings.h; sourceTree = "<group>"; };
+		3354AF4611ADBAFD006CF570 /* Actions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Actions.h; sourceTree = "<group>"; };
+		3354AF4711ADBAFD006CF570 /* Actions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Actions.mm; sourceTree = "<group>"; };
 		33627F9A0F7A82CE000C8818 /* training_albanian_SQ.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = training_albanian_SQ.txt; sourceTree = "<group>"; };
 		33627F9D0F7A82CE000C8818 /* training_bengali_BD.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = training_bengali_BD.txt; sourceTree = "<group>"; };
 		33627F9F0F7A82CE000C8818 /* training_canna_JP.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = training_canna_JP.txt; sourceTree = "<group>"; };
@@ -722,6 +738,7 @@
 		33CBB4EA101FA33E00510BF9 /* alphabet.portuguese.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = alphabet.portuguese.xml; sourceTree = "<group>"; };
 		33CBB4EB101FA33E00510BF9 /* alphabet.swedish.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = alphabet.swedish.xml; sourceTree = "<group>"; };
 		33CBB4F4101FA35900510BF9 /* training_basque_ES.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = training_basque_ES.txt; sourceTree = "<group>"; };
+		33DA5E2C11B70FA100011CD2 /* paste.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = paste.png; sourceTree = "<group>"; };
 		33EB483B0F7287DC0048E7C2 /* CDasherScreenBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDasherScreenBridge.h; sourceTree = "<group>"; };
 		33EB483C0F7287DC0048E7C2 /* CDasherScreenBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CDasherScreenBridge.mm; sourceTree = "<group>"; };
 		33EB48400F72A5680048E7C2 /* DasherScreenCallbacks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DasherScreenCallbacks.h; sourceTree = "<group>"; };
@@ -794,6 +811,8 @@
 				28FD14FD0DC6FC130079059D /* EAGLView.mm */,
 				1D3623240D0F684500981E51 /* DasherAppDelegate.h */,
 				1D3623250D0F684500981E51 /* DasherAppDelegate.mm */,
+				3354AF4611ADBAFD006CF570 /* Actions.h */,
+				3354AF4711ADBAFD006CF570 /* Actions.mm */,
 				33EB48400F72A5680048E7C2 /* DasherScreenCallbacks.h */,
 				3376EBFC0FC15A7300C4DC9F /* PlainDragFilter.h */,
 				3376EBFD0FC15A7300C4DC9F /* PlainDragFilter.cpp */,
@@ -841,13 +860,20 @@
 		29B97317FDCFA39411CA2CEA /* Resources */ = {
 			isa = PBXGroup;
 			children = (
+				3302678A11B8040D00C07880 /* spanner_lg.png */,
+				3302678B11B8040D00C07880 /* spanner.png */,
 				332F34D8103D9858008448D7 /* palette.png */,
 				334AC9B7102AE19400CE6871 /* Locked.png */,
+				3302672611B7F0D000C07880 /* copy.png */,
 				3334D4D21014740B0077948A /* misc.png */,
+				33DA5E2C11B70FA100011CD2 /* paste.png */,
 				3334D4BB1014713B0077948A /* globe.png */,
 				3334D4A2101385060077948A /* tilt.png */,
 				3334D49110137A1C0077948A /* pen.png */,
+				3302678011B8026900C07880 /* scissors.png */,
 				3334D44B1013620D0077948A /* cog.png */,
+				3302675411B7F80800C07880 /* bubble.png */,
+				3302675511B7F80800C07880 /* bubbletrash.png */,
 				33F61A5410123FBE00DB7685 /* mail.png */,
 				33F87A710FB1CB91003E737C /* Dasher_small.png */,
 				33F87A230FB1C775003E737C /* MainWindow.xib */,
@@ -1534,6 +1560,13 @@
 				332F34AF103D8F54008448D7 /* Makefile.am in Resources */,
 				332F34D9103D9858008448D7 /* palette.png in Resources */,
 				333F707711A8AA66002E2BDF /* us_pos.tree in Resources */,
+				33DA5E2D11B70FA100011CD2 /* paste.png in Resources */,
+				3302672811B7F0D000C07880 /* copy.png in Resources */,
+				3302675611B7F80800C07880 /* bubble.png in Resources */,
+				3302675711B7F80800C07880 /* bubbletrash.png in Resources */,
+				3302678111B8026900C07880 /* scissors.png in Resources */,
+				3302678C11B8040D00C07880 /* spanner_lg.png in Resources */,
+				3302678D11B8040D00C07880 /* spanner.png in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1712,6 +1745,7 @@
 				333F707811A8AA66002E2BDF /* us_pos_cart.c in Sources */,
 				333F707911A8AA66002E2BDF /* us_text.c in Sources */,
 				333F707A11A8AA66002E2BDF /* usenglish.c in Sources */,
+				3354AF4811ADBAFD006CF570 /* Actions.mm in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/Src/iPhone/bubble.png b/Src/iPhone/bubble.png
new file mode 100644
index 0000000..afbe08a
Binary files /dev/null and b/Src/iPhone/bubble.png differ
diff --git a/Src/iPhone/bubbletrash.png b/Src/iPhone/bubbletrash.png
new file mode 100644
index 0000000..1287e53
Binary files /dev/null and b/Src/iPhone/bubbletrash.png differ
diff --git a/Src/iPhone/copy.png b/Src/iPhone/copy.png
new file mode 100644
index 0000000..07e9ccb
Binary files /dev/null and b/Src/iPhone/copy.png differ
diff --git a/Src/iPhone/paste.png b/Src/iPhone/paste.png
new file mode 100644
index 0000000..7d775f0
Binary files /dev/null and b/Src/iPhone/paste.png differ
diff --git a/Src/iPhone/scissors.png b/Src/iPhone/scissors.png
new file mode 100644
index 0000000..acc7ebd
Binary files /dev/null and b/Src/iPhone/scissors.png differ
diff --git a/Src/iPhone/spanner.png b/Src/iPhone/spanner.png
new file mode 100644
index 0000000..dce8939
Binary files /dev/null and b/Src/iPhone/spanner.png differ
diff --git a/Src/iPhone/spanner_lg.png b/Src/iPhone/spanner_lg.png
new file mode 100644
index 0000000..2a31edf
Binary files /dev/null and b/Src/iPhone/spanner_lg.png differ



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]