Sunday, October 31, 2010

.NET ConditionalAttribute

The preface to this blog post is found here Windows Forms .NET handling unhandled exceptions. The short story being that I have a smart client / winforms application running on the clients machine in release mode. All unhandled exceptions caught are reported to a sharepoint list. However in debug mode on the developer machine we do not want to handle unhandled exceptions since this can be useful when debugging in Visual Studio.

Hence we need to differentiate between debug and release mode.

This have traditionally been done using the compiler directives as here below
static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
#if DEBUG
            // To be able to debug properly we do not want to steal the exceptions from VS IDE
            safeMain();
#else
            // In release mode we want to be exception safe
                        try
            {
                    safeMain();
            }
            catch (Exception exception)
            {
                    handleUnhandledExceptions(exception);
            }
#endif
        }

        /// <summary>
        /// safeMain is a try catch all for any possible unhandled exceptions
        /// </summary>
        static void safeMain()
        {
#if !DEBUG 
            // In release mode we want to be exception safe
            // CLR unhandled exceptions handler
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
            // Windows forms exceptions handler
            Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
#endif
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
This however is both messy and can lead to mistakes as I will show later.

The .NET framework have a useful attribute class that makes arranging this code in a better way very easy. The ConditionalAttribute.

Lets look as some code
using System;
using System.Linq;
using System.Windows.Forms;
using System.Diagnostics;
using System.Reflection;

namespace ConditionalAttrb
{
 public partial class Form1 : Form
 {
  public Form1()
  {
   InitializeComponent();
  }

  private void button1_Click(object sender, EventArgs e)
  {
   Log();
  }

  [Conditional("DEBUG")]
  private void Log()
  {
   label1.Text = "Inside Log()";
  }

  private void Form1_Load(object sender, EventArgs e)
  {
   if (IsAssemblyDebugBuild(this.GetType().Assembly))
   {
    this.Text = "Conditional Attribute (Debug)";
   }
   else
   {
    this.Text = "Conditional Attribute (Release)";
   }
  }

  private bool IsAssemblyDebugBuild(Assembly assembly)
  {
   return assembly.GetCustomAttributes(false).Any(x => (x as DebuggableAttribute) != null ? (x as DebuggableAttribute).IsJITTrackingEnabled : false);
  }
 }
}
The Form1_Load event handler method is only here used to set the correct title on the form so it is easy to see if you are in debug or release mode. It uses the IsAssemblyDebugBuild helper method for this.

The 2 only methods that are really relevant to the ConditionalAttribute class is the button1_Click event handler method and the Log() method.

You can see from the screenshots here below what happens when you run the application in debug / release mode and click the button.




This is more or less to be expected after all we only want the logging to happen when in debug mode.

So what is going on behind the scenes, lets have a look at the MSIL code for the Log() in debug and then release mode.

Debug:
.method private hidebysig instance void  Log() cil managed
{
  .custom instance void [mscorlib]System.Diagnostics.ConditionalAttribute::.ctor(string) = ( 01 00 05 44 45 42 55 47 00 00 )                   // ...DEBUG..
  // Code size       19 (0x13)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldfld      class [System.Windows.Forms]System.Windows.Forms.Label ConditionalAttrb.Form1::label1
  IL_0007:  ldstr      "Inside Log()"
  IL_000c:  callvirt   instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string)
  IL_0011:  nop
  IL_0012:  ret
} // end of method Form1::Log

Release:
.method private hidebysig instance void  Log() cil managed
{
  .custom instance void [mscorlib]System.Diagnostics.ConditionalAttribute::.ctor(string) = ( 01 00 05 44 45 42 55 47 00 00 )                   // ...DEBUG..
  // Code size       17 (0x11)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldfld      class [System.Windows.Forms]System.Windows.Forms.Label ConditionalAttrb.Form1::label1
  IL_0006:  ldstr      "Inside Log()"
  IL_000b:  callvirt   instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string)
  IL_0010:  ret
} // end of method Form1::Log
If we study the MSIL code closely we see that the code is the same apart from 2 nop.

From this we can conclude that the method Log() is the same in debug and release mode. Now let us have a look at the calling button1_Click() method.

Debug:
.method private hidebysig instance void  button1_Click(object sender,
                                                       class [mscorlib]System.EventArgs e) cil managed
{
  // Code size       9 (0x9)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  call       instance void ConditionalAttrb.Form1::Log()
  IL_0007:  nop
  IL_0008:  ret
} // end of method Form1::button1_Click

Release:
.method private hidebysig instance void  button1_Click(object sender,
                                                       class [mscorlib]System.EventArgs e) cil managed
{
  // Code size       1 (0x1)
  .maxstack  8
  IL_0000:  ret
} // end of method Form1::button1_Click
If we look at this MSIL code we can see that it is the calling function that is modified not the Log() method. Since the code is JIT'ed the extra Log() method does not incur a performance overhead in release mode since the JIT compiler looks ahead and only JITs methods that will be called.

Ok so what is the benefit apart from that my code looks better?

Assuming that you are writing the following code
using System;
using System.Linq;
using System.Windows.Forms;
using System.Diagnostics;
using System.Reflection;

namespace ConditionalAttrb
{
 public partial class Form1 : Form
 {
  public Form1()
  {
   InitializeComponent();
  }

  private void button1_Click(object sender, EventArgs e)
  {
   string message = null;
#if DEBUG
   log = "Logging";
#endif 
   Log(message);
  }

  private void Log(string message)
  {
   if (message == null) throw new ArgumentNullException("message");
   label1.Text = message;
  }

  private void Form1_Load(object sender, EventArgs e)
  {
   if (IsAssemblyDebugBuild(this.GetType().Assembly))
   {
    this.Text = "Conditional Attribute (Debug)";
   }
   else
   {
    this.Text = "Conditional Attribute (Release)";
   }
  }

  private bool IsAssemblyDebugBuild(Assembly assembly)
  {
   return assembly.GetCustomAttributes(false).Any(x => (x as DebuggableAttribute) != null ? (x as DebuggableAttribute).IsJITTrackingEnabled : false);
  }
 }
}

This is a contrived example however it is not unusual for conditional code to become rather complex over the time of a project. And this code is actually introducing a fatal flaw in release mode which is not present in debug mode.



Ideally the code base should be the same in debug and release mode, and conditional compiler directives is in IMHO a bad idea when we have the ConditionalAttribute.

Saturday, October 30, 2010

Windows Forms .NET handling unhandled exceptions

This posting is a lead in to another post I am going to write about the ConditionalAttribute in the .NET framework. Since this posting has its own subject as well as setting the scene for the next posting I decided to keep them as 2 separate posts.

This code was written for a project some 3 years ago but is still relevant. The code was built based on a lot of investigations as well as online resources. Since it is some time ago the code was written I will not be able to mention all the sources, however the code was heavily inspired by 


Goals: Handle any unhandled exceptions in a windows forms application, log them to an arbitrary log location (Webservice, Sharepoint list or similar) as well as gracefully exit the windows application after encountering an unhandled exception.

First lets take a look at the Program.cs file that is containing the static Main() method.

using System;
using System.Windows.Forms;
using System.Threading;

namespace WinFormsUnhandledException
{
 static class Program
 {
  /// <summary>
  /// The main entry point for the application.
  /// </summary>
  [STAThread]
  static void Main()
  {
#if DEBUG
   // To be able to debug properly we do not want to steal the exceptions from VS IDE
   safeMain();
#else
   // In release mode we want to be exception safe
   try
   {
     safeMain();
   }
   catch (Exception exception)
   {
     handleUnhandledExceptions(exception);
   }
#endif
  }

  /// <summary>
  /// safeMain is a try catch all for any possible unhandled exceptions
  /// </summary>
  static void safeMain()
  {
#if !DEBUG 
   // In release mode we want to be exception safe
   // CLR unhandled exceptions handler
   AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
   // Windows forms exceptions handler
   Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
#endif
   Application.EnableVisualStyles();
   Application.SetCompatibleTextRenderingDefault(false);
   Application.Run(new MainForm());
  }

  /// <summary>
  /// Handles any unhandled windows exceptions. The applicatin can continue running
  /// after this.
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
  {
   handleUnhandledExceptions(e.Exception);
  }

  /// <summary>
  /// Handles any unhandles CLR exceptions. Note that in .NET 2.0 unless you set
  /// <legacyUnhandledExceptionPolicy enabled="1"/> in the app.config file your
  /// application will exit no matter what you do here anyway. The purpose of this
  /// eventhandler is not forcing the application to continue but rather logging 
  /// the exception and enforcing a clean exit where all the finalizers are run 
  /// (releasing the resources they hold)
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
  {
   handleUnhandledExceptions(e.ExceptionObject);

   // Prepare cooperative async shutdown from another thread
   Thread t = new Thread
   (
    delegate()
    {
     Environment.Exit(1);
    }
   );
   t.Start();
   t.Join();
  }


  /// <summary>
  /// Reports the unhandled exceptions
  /// </summary>
  /// <param name="exception"></param>
  static void handleUnhandledExceptions(object exception)
  {
   Exception e = exception as Exception;
   if (e != null)
   {
    ExceptionHelper.ReportException(e, new SpsSoftwareBugReporter(), new ReporterConfiguration("http://MYSHAREPOINTSERVER/SITE1/SITE2/_vti_bin/Lists.asmx"), string.Empty, true);
   }
  }
 }
}

There are several relevant points in this way to start a windows forms application and the comments in the code explains pretty well what is going on. Please note how cluttered the compiler directives for DEBUG ... is. These are the reason for my upcoming post on the ConditionalAttribute.

Basically this code in the Program.cs allows us to do the following in the main form.




MainForm.cs

using System;
using System.Windows.Forms;
using System.Threading;

namespace WinFormsUnhandledException
{
 public partial class MainForm : Form
 {
  public MainForm()
  {
   InitializeComponent();
  }

  private void button1_Click(object sender, EventArgs e)
  {
   throw new ArgumentNullException("something");
  }

  private void button2_Click(object sender, EventArgs e)
  {
   Thread.CurrentThread.Abort();
  }
 }
}

The Thread.CurrentThread.Abort(); line was specifically put there to show that this specific exception is a little bit different than the rest. This will in the current version of the code posted here show the exception dialog more than 1 time and potentially quite a few.  I will not go deep into Thread.Abort() now, but in short - don't use it. There are many articles about this on the net but a good reference I like is

Aborting Threads


Clicking on the 2 buttons above will show the following forms.




How do we get this form to appear and where do we get the information from? This is all happening in the ExceptionHelper class.


using System;
using System.Security;
using System.Reflection;
using System.Text;
using System.Diagnostics;
using System.Windows.Forms;

namespace WinFormsUnhandledException
{
 public class ExceptionHelper
 {
  /// <summary>
  /// Reports an exception a central place. This would most likely
  /// find a use in Windows smart clients in the 
  /// AppDomain.CurrentDomain.UnhandledException handler and the
  /// Application.ThreadException handler
  /// </summary>
  /// <param name="exception">The exception</param>
  /// <param name="reporter">The logic that takes care of actually reporting the error to the place you want it</param>
  /// <param name="reporterConfiguration">Storage configuration for the reporter</param>
  /// <param name="userComments">User comments about the error</param>
  /// <param name="showForm">True - show error form and ask user to accept report.
  /// False - dont show form just report</param>
  public static void ReportException(Exception exception, IReporter<UnhandledExceptionReport> reporter, ReporterConfiguration reporterConfiguration, string userComments, bool showForm)
  {
   StringBuilder sb = new StringBuilder();
   Assembly a = Assembly.GetEntryAssembly();
   UnhandledExceptionReport unhandledExceptionReport = new UnhandledExceptionReport();
   unhandledExceptionReport.Application = a.FullName;

   AssemblyName[] references = a.GetReferencedAssemblies();
   foreach (AssemblyName reference in references)
   {
    sb.Append(reference.FullName);
    sb.Append(Environment.NewLine);
   }

   unhandledExceptionReport.Assemblies = sb.ToString();
   unhandledExceptionReport.Date = DateTime.Now.ToString();
   unhandledExceptionReport.Exceptions = exception.ToString();
   unhandledExceptionReport.FileAttachments = new string[] { GetSystemInfo() };
   unhandledExceptionReport.UserComments = userComments;

   if (showForm)
   {
    UnhandledExceptionReportForm form = new UnhandledExceptionReportForm(unhandledExceptionReport);
    if (form.ShowDialog() == DialogResult.OK)
    {
     reporter.Submit(unhandledExceptionReport);
    }
   }
   else
   {
    reporter.Submit(unhandledExceptionReport);
   }
  }

  
  /// <summary>
  /// Calls the MSINFO32.exe and generates a nfo file. The path of the 
  /// generated file is returned.
  /// </summary>
  /// <returns>Path of generated msinfo32 report file</returns>
  public static string GetSystemInfo()
  {
   try
   {
    // retrieve the path to MSINFO32.EXE
    Microsoft.Win32.RegistryKey key;
    key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Shared Tools\MSInfo");
    if (key != null)
    {
     string exeFile = key.GetValue("Path", "").ToString();
     key.Close();

     if (exeFile.Length != 0)
     {
      ProcessStartInfo processStartInfo = new ProcessStartInfo();
      processStartInfo.FileName = exeFile;
      processStartInfo.Arguments = "/nfo " + Environment.GetEnvironmentVariable("TEMP") + "\\sysinfo /categories +systemsummary-Resources-Components-SWEnv-InternetSettings-Apps11+SWEnvEnvVars+SWEnvRunningTasks+SWEnvServices";
      Process process = Process.Start(processStartInfo);
      process.WaitForExit();
     }
    }
    return Environment.GetEnvironmentVariable("TEMP") + "\\sysinfo.nfo";
   }
   catch (SecurityException)
   {
    // mostlikely due to not having the correct permissions
    // to access the registry, ignore this exception
    return null;
   }
  }
 }
}


This class gathers the information about the exception as well as a system information dump and shows the Unhandled Exception Form where the user can enter some information to explain what they did to provoke the exception.

I am using the following support classes
using System;

namespace WinFormsUnhandledException
{
 /// 
 /// 
 /// 
 public class UnhandledExceptionReport
 {
  public UnhandledExceptionReport()
  { 
   Title = GetType().Name;
  }
  public string Title {get; set;}
  public string Application {get; set;}
  public string Date {get; set;}
  public string UserComments {get; set;}
  public string Exceptions {get; set;}
  public string Assemblies {get; set;}
  public string[] FileAttachments { get; set; }
 }
}

Here is the code for the UnhandledExceptionReportForm

using System;
using System.Windows.Forms;
using System.Diagnostics;

namespace WinFormsUnhandledException
{
 public partial class UnhandledExceptionReportForm : Form
 {
  private UnhandledExceptionReport m_unhandledExceptionReport;

  public UnhandledExceptionReportForm()
  {
   InitializeComponent();
  }
  public UnhandledExceptionReportForm(UnhandledExceptionReport unhandledExceptionReport) : this()
  {
   this.m_unhandledExceptionReport = unhandledExceptionReport;
   if (m_unhandledExceptionReport != null && m_unhandledExceptionReport.UserComments == string.Empty)
   {
    m_unhandledExceptionReport.UserComments = "Please write a description of what you were doing when the error occured";
   }
   
   titleTextBox.Text = m_unhandledExceptionReport.Title;
   applicationTextBox.Text = m_unhandledExceptionReport.Application;
   dateTextBox.Text = m_unhandledExceptionReport.Date;
   commentsTextBox.DataBindings.Add("Text", m_unhandledExceptionReport, "UserComments");
   exceptionsTextBox.Text = m_unhandledExceptionReport.Exceptions;
   assembliesTextBox.Text = m_unhandledExceptionReport.Assemblies;
   attachmentLinkLabel.Text = m_unhandledExceptionReport.FileAttachments[0];
  }

  private void reportButton_Click(object sender, EventArgs e)
  {
   Hide();
   DialogResult = DialogResult.OK;
  }

  private void cancelButton_Click(object sender, EventArgs e)
  {
   Hide();
   DialogResult = DialogResult.Cancel;
  }

  private void attachmentLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
  {
   Process.Start(m_unhandledExceptionReport.FileAttachments[0]);
  }

 }
}

Finally we have the interface for the class doing the actual error reporting

using System;

namespace WinFormsUnhandledException
{
 
 public interface IReporter<T>
 {
  /// 
  /// Reports some information to a specific location
  /// 
  /// The type to be reported on  
  /// The configuration the reporter need  
  void Submit(T report, ReporterConfiguration config);
 }
}

and here is a IReported implementation that is reporting unhandled exceptions to a Sharepoint list.

using System;
using System.Text;
using System.Reflection;
using System.Net;
using System.Xml;
using System.IO;
using System.Xml.XPath;

using System.Security;

namespace WinFormsUnhandledException
{
 public class SpsSoftwareBugReporter : IReporter<UnhandledExceptionReport>
 {
  #region IReporter<SoftwareBug> Members

  void IReporter<UnhandledExceptionReport>.Submit(UnhandledExceptionReport report, ReporterConfiguration config)
  {
   ListsService.Lists service = new ListsService.Lists();
   service.Url = config.ConnectionString;
   service.Credentials = CredentialCache.DefaultCredentials;

   XmlNode list = service.GetList("Bugs");
   string listName = list.Attributes["ID"].Value;

   StringBuilder sb = new StringBuilder();
   sb.Append("<Method ID=\"1\" Cmd=\"New\">");
   sb.Append("<Field Name=\"ID\">New</Field>");
   PropertyInfo[] properties = report.GetType().GetProperties();

   foreach (PropertyInfo propertyInfo in properties)
   {
    if (propertyInfo.Name != "FileAttachments")
    {
     sb.Append("<Field Name=\"");
     sb.Append(propertyInfo.Name);
     sb.Append("\">");
     sb.Append(SecurityElement.Escape(propertyInfo.GetValue(report, null).ToString()));
     sb.Append("</Field>");
    }
   }
   sb.Append("</Method>");

   XmlDocument doc = new XmlDocument();
   XmlElement newBugs = doc.CreateElement("Batch");
   newBugs.SetAttribute("OnError", "Continue");
   newBugs.SetAttribute("ListVersion", list.Attributes["Version"].Value);
   
   
   
   newBugs.InnerXml =  sb.ToString();
   // Add bug report
   XmlNode result = service.UpdateListItems(listName, newBugs);

   // Add item attachments
   XPathNavigator navigator = result.CreateNavigator();
   XmlNamespaceManager manager = new XmlNamespaceManager(navigator.NameTable);
   manager.AddNamespace("def", "http://schemas.microsoft.com/sharepoint/soap/");
   manager.AddNamespace("z", "#RowsetSchema");

   XPathNavigator node = navigator.SelectSingleNode("//def:ErrorCode", manager);
   if (node.InnerXml == "0x00000000")
   {
    XmlNodeList nodes = result.SelectNodes("//z:row", manager);
    
    foreach (string  attachment in report.FileAttachments)
    {
     if (attachment != null || attachment != string.Empty)
     {
      using (FileStream stream = File.OpenRead(attachment))
      {
       try
       {
        service.AddAttachment(listName, nodes[0].Attributes["ows_ID"].Value, Path.GetFileName(attachment), StreamHelper.ReadFully(stream));
       }
       catch (Exception exception)
       {
        Console.WriteLine(exception.ToString());
       }
      }
     }
    }
   }
  }
  #endregion
 }
}

Friday, October 15, 2010

SugarCrm 5.5.2 Editview: Fieldlevel localizable tooltip in Module Builder

Sugar and module builder supports field specific help that will be shown as tooltip of a control in the different views. There is unfortunately no localization support for this in sugar (meaning that the tooltips will always be in one language).
This posting and hack adds that localization support of vardefs field help text.

The way this works is:

1. Create or edit a field in Module Builder. (You will notice a new help text & help label field.)
2. The default language help text needs to be entered in help text. (There is no need to modify help label name.)



3. Save the field.
4. Go to labels localization screen, change the desired language, find the field help label and enter localized value then save the label. Put the field in one of the layouts. Then deploy package.




5. Based on login language, help tool tip value will be shown from language file.




This customization is nonupgrade safe.
The following files have been modified

1. include/EditView/EditView2.php
a. Following block added in function display to assign localized help value to editview.
//HACK : -- Add localization to vardef field_help    
foreach($this->fieldDefs as $key => $val){
 $tempk = ''; $tempv='';
 if(isset($this->fieldDefs[$key]['help'])){
  $tempk = $this->fieldDefs[$key]['help'];
 }else{
  $tempk = "LBL_".mb_strtoupper($this->fieldDefs[$key]['name'])."_HELP";
 }
 $tempv = translate($tempk, $this->module);         
 if($tempv!=$tempk){
 //HACK :-- Translating help fields
  //$this->fieldDefs[$key]['help'] = $tempv;
  if($this->fieldDefs[$key]['type']=='radioenum'){
   $this->fieldDefs[$key]['help'] = $tempk;
  }else{
   $this->fieldDefs[$key]['help'] = "{php}echo smarty_function_sugar_translate(array('label' => '$tempk' ,'module' => '$this->module'), \$this);{/php}";
  } 
 //HACK : -- Add localization to vardef field_help
 }else{
  $this->fieldDefs[$key]['help'] ='';  
 }
 if(isset($this->fieldDefs[$key]['validation']['functionName']) && !empty($this->fieldDefs[$key]['validation']['functionName'])){
  if(isset($this->fieldDefs[$key]['customjs'])){
     $tempscript.='{literal}<script type="text/javascript" language="Javascript">'.html_entity_decode($this->fieldDefs[$key]['customjs'],ENT_QUOTES).'</script>{/literal}';
  }
  if(isset($this->fieldDefs[$key]['validation'])&& is_array($this->fieldDefs[$key]['validation'])!=true){
     $this->fieldDefs[$key]['validation'] = unserialize(html_entity_decode($this->fieldDefs[$key]['validation']));
  }
 }
}
 $this->th->ss->assign('scriptBlocks', $tempscript);
//HACK :


2. include/SearchForm/SearchForm2.php
a. Following block added in function display to assign localized help value to editview.
//HACK : -- Add localization to vardef field_help  
foreach($this->fieldDefs as $key => $val){
 $tempk = ''; $tempv='';
 if(isset($this->fieldDefs[$key]['help'])){
  $tempk = $this->fieldDefs[$key]['help'];
 }else{
  $tempk = "LBL_".mb_strtoupper($this->fieldDefs[$key]['name'])."_HELP";
 }
 $tempv = translate($tempk, $this->module);         
 if($tempv!=$tempk){
 //HACK :-- Translating help fields
    //$this->fieldDefs[$key]['help'] = $tempv;
  if($this->fieldDefs[$key]['type']=='radioenum'){
   $this->fieldDefs[$key]['help'] = $tempk;
  }else{
   $this->fieldDefs[$key]['help'] = "{php}echo smarty_function_sugar_translate(array('label' => '$tempk' ,'module' => '$this->module'), \$this);{/php}";
  } 
 //HACK : -- Add localization to vardef field_help
 }else{
  $this->fieldDefs[$key]['help'] ='';  
 }
}
//HACK :


3. modules/DynamicFields/DynamicField.php
a. In function addFieldObject, value of $fmd->help set from $field->help_label.
//HACK : -- Add localization to vardef field_help
$fmd->help = $field->help_label;
//HACK : -- Add localization to vardef field_help


4. modules/DynamicFields/templates/Fields/Forms/coreTop.tpl
a. Template modified for help label & its automatic assignment of value based on its field name.
<table width="100%">
<tr>
 <td class='mbLBL' width='30%' >{$MOD.COLUMN_TITLE_NAME}:</td>
 <td>
 {if $hideLevel == 0}
  {* HACK:  -- Add localization to vardef field_help 
   Added javascript statement for automatic help label assignment *}
  <input id="field_name_id" maxlength=30 type="text" name="name" value="{$vardef.name}" onchange="document.getElementById('label_key_id').value = 'LBL_'+document.getElementById('field_name_id').value.toUpperCase();
  document.getElementById('label_value_id').value = document.getElementById('field_name_id').value.replace(/_/,' ');
  document.getElementById('field_name_id').value = document.getElementById('field_name_id').value.toLowerCase();  
  document.getElementById('help_key_id').value = 'LBL_'+(document.getElementById('field_name_id').value.toUpperCase())+'_HELP';" />
  {* HACK:  *}
 {else}
  {* HACK:  -- Add localization to vardef field_help 
   Added javascript statement for automatic help label assignment *}
  <input id= "field_name_id" maxlength=30 type="hidden" name="name" value="{$vardef.name}" 
    onchange="document.getElementById('label_key_id').value = 'LBL_'+document.getElementById('field_name_id').value.toUpperCase();
  document.getElementById('label_value_id').value = document.getElementById('field_name_id').value.replace(/_/,' ');
  document.getElementById('field_name_id').value = document.getElementById('field_name_id').value.toLowerCase();
  document.getElementById('help_key_id').value = 'LBL_'+(document.getElementById('field_name_id').value.toUpperCase())+'_HELP';"/>{$vardef.name}{/if}
  {* HACK: *}
  <script>
  addToValidate('popup_form', 'name', 'DBName', true,'{$MOD.COLUMN_TITLE_NAME} [a-zA-Z_]' );
  addToValidateIsInArray('popup_form', 'name', 'in_array', true,'{$MOD.ERR_RESERVED_FIELD_NAME}', '{$field_name_exceptions}', 'u==');
  </script>
 </td>
</tr>
<tr>
 <td class='mbLBL'>{$MOD.COLUMN_TITLE_DISPLAY_LABEL}:</td>
 <td>
  <input id="label_value_id" type="text" name="labelValue" value="{$lbl_value}">
 </td>
</tr>
<tr>
 <td class='mbLBL'>{$MOD.COLUMN_TITLE_LABEL}:</td>
 <td>
 {if $hideLevel < 5}
  <input id ="label_key_id" type="text" name="label" value="{$vardef.vname}">
 {else}
  <input id ="label_key_id" type="hidden" name="label" value="{$vardef.vname}">{$vardef.vname}
 {/if}
 </td>
</tr>
{* HACK:  -- Add localization to vardef field_help ,Course module Accounting type does not work *}
<tr>
 <td class='mbLBL'>{$MOD.COLUMN_TITLE_HELP_TEXT}:</td>
 <td>
  <input type="text" id="help_value_id" name="help" value="{$vardef.help}" onchange="if(document.getElementById('help_key_id').value == '') document.getElementById('help_key_id').value='LBL_'+(document.getElementById('field_name_id').value.toUpperCase())+'_HELP';">
 </td>
</tr>

<tr>
 <td class='mbLBL'>{$MOD.COLUMN_TITLE_HELP_LABEL}:</td>
 <td>{if $hideLevel < 5 }
  <input type="text" id="help_key_id" name="help_label" value="{$vardef.help_label}">
  {else}
  <input type="hidden" id="help_key_id" name="help_label" value="{$vardef.help_label}">{$vardef.help_label}
  {/if}
 </td>
</tr>
{* HACK: *}
<tr>
    <td class='mbLBL'>{$MOD.COLUMN_TITLE_COMMENT_TEXT}:</td><td>{if $hideLevel < 5 }<input type="text" name="comments" value="{$vardef.comments}">{else}<input type="hidden" name="comment" value="{$vardef.comment}">{$vardef.comment}{/if}
    </td>
</tr>


5. modules/DynamicFields/templates/Fields/TemplateField.php
a. New class variable added
//HACK : -- Add localization to vardef field_help
// for initialisation of bean value.
var $help_label ='';
//HACK : -- Add localization to vardef field_help

b. New key value pair added to array for new vardef property help_label
var $vardef_map = array(
 'name'=>'name',
 'label'=>'vname',
 // bug 15801 - need to ALWAYS keep default and default_value consistent as some methods/classes use one, some use another...
 'default_value'=>'default',
 'default'=>'default_value',
 //'default_value'=>'default_value',
 //'default'=>'default_value',
 'len'=>'len',
 'required'=>'required',
 'type'=>'type',
 'audited'=>'audited',
 'massupdate'=>'massupdate',
 'options'=>'ext1',
 'help'=>'help',
 'comments'=>'comment',
 'importable'=>'importable',
 'duplicate_merge'=>'duplicate_merge',
 //'duplicate_merge_dom_value'=>'duplicate_merge', //bug #14897
 'reportable' => 'reportable',
 'min'=>'ext1',
 'max'=>'ext2',
 'ext2'=>'ext2',
 'ext4'=>'ext4',
 //'disable_num_format'=>'ext3',
 'ext3'=>'ext3',
 'label_value'=>'label_value',

 //HACK :  -- Add localization to vardef field_help
 //used for vardef mapping.
 'help_label'=>'help_label',
 //HACK :  
);


6. modules/EditCustomFields/FieldsMetaData.php
a. In class FieldsMetaData 2 new class variable added.
//HACK : -- Add localization to vardef field_help
var $help;
var $help_label;
//HACK : -- Add localization to vardef field_help

b. 2 New key value pair added to array for new vardef property .
var $column_fields = array(
 'id',
 'name',
 'vname',
 'custom_module',
 'type',
 'len',
 'required',
 'default_value',
 'deleted',
 'ext1',
 'ext2',
 'ext3',
 //HACK : -- Add localization to vardef field_help
 'help',
 'help_label', 
 //HACK : 
 'audited',
 'massupdate',
 'duplicate_merge',
 'reportable',
);


7. modules/EditCustomFields/language/en_us.lang.php
a. Help label
// HACK : -- Add localization to vardef field_help
// label for the new help label 
$mod_strings['COLUMN_TITLE_HELP_LABEL'] = 'Help Label';
// HACK : -- Add localization to vardef field_help


8. modules/EditCustomFields/vardefs.php
a. New field added for help_label.
// HACK : -- Add localization to vardef field_help 
// Added for adding new field help label in module builder. 
$dictionary['FieldsMetaData']['fields']['help_label']=array('name' =>'help_label' ,'type' =>'varchar','vname'=>'COLUMN_TITLE_LABEL', 'len'=>'255');
// HACK : -- Add localization to vardef field_help


9. modules/ModuleBuilder/controller.php
a. function action_SaveLabel modified to set value of help_label.
// HACK : -- Add localization to vardef field_help
 if (isset($_REQUEST['help_label']) && isset ($_REQUEST['help' ])){
 if(empty($_REQUEST['help_label'])){
  $_REQUEST['help_label']="LBL_".$_REQUEST['name']."_HELP";
 }
 $_REQUEST [ "label_" . $_REQUEST['help_label'] ] = $_REQUEST [ 'help' ] ;
 } 
// HACK : -- Add localization to vardef field_help

b. function action_SaveField is modified to set help_label value in vardefs before saving mbvardefs & value of label in language file of current language before saving modue.
//HACK : -- Add localization to vardef field_help
if(isset($_REQUEST['help_label']) && empty($_REQUEST['help_label']))
  $_REQUEST['help_label']="LBL_".mb_strtoupper($_REQUEST['name'])."_HELP";         

$module->mbvardefs->vardef['fields'][$_REQUEST['name']]['help']=$_REQUEST['help_label'];
//HACK :
$module->mbvardefs->save () ;
// get the module again to refresh the labels we might have saved with the $field->save (e.g., for address fields)
$module = & $mb->getPackageModule ( $_REQUEST [ 'view_package' ], $_REQUEST [ 'view_module' ] ) ;
if (isset ( $_REQUEST [ 'label' ] ) && isset ( $_REQUEST [ 'labelValue' ] ))
 $module->setLabel ( $GLOBALS [ 'current_language' ], $_REQUEST [ 'label' ], $_REQUEST [ 'labelValue' ] ) ;
//HACK : -- Add localization to vardef field_help
//setting the help text and label for current language.            
if (isset($_REQUEST['help_label']) && isset ($_REQUEST['help']))
 $module->setLabel($GLOBALS['current_language'],$_REQUEST['help_label'],$_REQUEST['help']);         
//HACK :


10. modules/ModuleBuilder/views/view.modulefield.php
a. Code block added to set value fo $vardef[‘help’] and $vardef[‘help_label’]. Following code block is for studio code.
//HACK : -- Add localization to vardef field_help
$temp_var_help_translation='';
if(isset($vardef['help'])){
 if(!empty($vardef['help'])){
  $temp_var_help_translation=translate(mb_strtoupper($vardef['help']),$moduleName);
  if($temp_var_help_translation != mb_strtoupper($vardef['help'])){
   $vardef['help_label']=$vardef['help'];
   $vardef['help']=$temp_var_help_translation;
  }else{
   $vardef['help_label']=$vardef['help'];
   $vardef['help']='';      
  }
 }
}elseif(!empty($vardef['name'])){ 
 $tempkey="LBL_".mb_strtoupper($vardef['name'])."_HELP";
 $temp_var_help_translation = translate(mb_strtoupper($tempkey),$moduleName);
 if($temp_var_help_translation != mb_strtoupper($tempkey)){
  $vardef['help_label']=$tempkey;
  $vardef['help']=$temp_var_help_translation;
 }else{
  $vardef['help_label']=$tempkey;
  $vardef['help']='';          
 }
}
//HACK :

b. Code block added to set value fo $vardef[‘help’] and $vardef[‘help_label’]. Following code block is for module builder code.
//HACK : -- Add localization to vardef field_help
$temp_var_help_translation='';
if(isset($vardef['help'])){ 
 if(!empty($vardef['help'])){
  //HACK:Upgrade sugarbase to SugarCRM 5.5
  $temp_var_help_translation=$module->getLabel($GLOBALS['current_language'],$vardef['help']);
  //HACK:Upgrade sugarbase to SugarCRM 5.5
  if(!empty($temp_var_help_translation)){
   $vardef['help_label']=$vardef['help'];
   $vardef['help']=$temp_var_help_translation;
  }else{
   $vardef['help_label']=$vardef['help'];
   $vardef['help']='';      
  }
 }
}elseif(!empty($vardef['name'])){ 
 $tempkey="LBL_".mb_strtoupper($vardef['name'])."_HELP";
 //HACK:Upgrade sugarbase to SugarCRM 5.5
 $temp_var_help_translation=$module->getLabel($GLOBALS['current_language'],$tempkey);
 //HACK:Upgrade sugarbase to SugarCRM 5.5
 if(!empty($temp_var_help_translation)){
  $vardef['help_label']=$tempkey;
  $vardef['help']=$temp_var_help_translation;
 }else{
  $vardef['help_label']=$tempkey;
  $vardef['help']='';          
 }
}
//HACK :


11. Add new field in fields_meta_data table by running following query.
ALTER TABLE fields_meta_data 
add column help_label varchar (255) Null

Changes made in language file & vardefs files are not upgrade safe. While building package move it to upgrade-safe files.

And finally the SVN diff containing the changes


Index: include/EditView/EditView2.php
===================================================================
--- include/EditView/EditView2.php (revision 1)
+++ include/EditView/EditView2.php (working copy)
@@ -447,6 +447,38 @@
         $this->th->ss->assign('offset', $this->offset + 1);
         $this->th->ss->assign('APP', $app_strings);
         $this->th->ss->assign('MOD', $mod_strings);
+  //HACK : -- Add localization to vardef field_help    
+        foreach($this->fieldDefs as $key => $val){
+            $tempk = ''; $tempv='';
+            if(isset($this->fieldDefs[$key]['help'])){
+             $tempk = $this->fieldDefs[$key]['help'];
+            }else{
+             $tempk = "LBL_".mb_strtoupper($this->fieldDefs[$key]['name'])."_HELP";
+            }
+         $tempv = translate($tempk, $this->module);         
+            if($tempv!=$tempk){
+   //HACK :-- Translating help fields
+                //$this->fieldDefs[$key]['help'] = $tempv;
+    if($this->fieldDefs[$key]['type']=='radioenum'){
+                 $this->fieldDefs[$key]['help'] = $tempk;
+                }else{
+     $this->fieldDefs[$key]['help'] = "{php}echo smarty_function_sugar_translate(array('label' => '$tempk' ,'module' => '$this->module'), \$this);{/php}";
+    } 
+   //HACK : 
+            }else{
+             $this->fieldDefs[$key]['help'] ='';  
+            }
+            if(isset($this->fieldDefs[$key]['validation']['functionName']) && !empty($this->fieldDefs[$key]['validation']['functionName'])){
+    if(isset($this->fieldDefs[$key]['customjs'])){
+                $tempscript.='{literal}<script type="text/javascript" language="Javascript">'.html_entity_decode($this->fieldDefs[$key]['customjs'],ENT_QUOTES).'</script>{/literal}';
+             }
+             if(isset($this->fieldDefs[$key]['validation'])&& is_array($this->fieldDefs[$key]['validation'])!=true){
+                $this->fieldDefs[$key]['validation'] = unserialize(html_entity_decode($this->fieldDefs[$key]['validation']));
+             }
+   }
+        }
+         $this->th->ss->assign('scriptBlocks', $tempscript);
+        //HACK : 
         $this->th->ss->assign('fields', $this->fieldDefs);
         $this->th->ss->assign('sectionPanels', $this->sectionPanels);
         $this->th->ss->assign('returnModule', $this->returnModule);
Index: include/SearchForm/SearchForm2.php
===================================================================
--- include/SearchForm/SearchForm2.php (revision 1)
+++ include/SearchForm/SearchForm2.php (working copy)
@@ -161,7 +161,29 @@
   if($this->nbTabs>1){
       $this->th->ss->assign('TABS', $this->_displayTabs($this->module . '|' . $this->displayView));
   }
-
+      //HACK : -- Add localization to vardef field_help  
+      foreach($this->fieldDefs as $key => $val){
+            $tempk = ''; $tempv='';
+            if(isset($this->fieldDefs[$key]['help'])){
+             $tempk = $this->fieldDefs[$key]['help'];
+            }else{
+             $tempk = "LBL_".mb_strtoupper($this->fieldDefs[$key]['name'])."_HELP";
+            }
+         $tempv = translate($tempk, $this->module);         
+            if($tempv!=$tempk){
+   //HACK :-- Translating help fields
+               //$this->fieldDefs[$key]['help'] = $tempv;
+       if($this->fieldDefs[$key]['type']=='radioenum'){
+                 $this->fieldDefs[$key]['help'] = $tempk;
+                }else{
+     $this->fieldDefs[$key]['help'] = "{php}echo smarty_function_sugar_translate(array('label' => '$tempk' ,'module' => '$this->module'), \$this);{/php}";
+    } 
+   //HACK : 
+            }else{
+             $this->fieldDefs[$key]['help'] ='';  
+            }
+        }
+        //HACK :
   $this->th->ss->assign('fields', $this->fieldDefs);
   $this->th->ss->assign('customFields', $this->customFieldDefs);
   $this->th->ss->assign('formData', $this->formData);
Index: modules/DynamicFields/DynamicField.php
===================================================================
--- modules/DynamicFields/DynamicField.php (revision 1)
+++ modules/DynamicFields/DynamicField.php (working copy)
@@ -507,7 +507,9 @@
         $fmd->name = $db_name;
         $fmd->vname = $label;
         $fmd->type = $field->type;
-        $fmd->help = $field->help;
+        //HACK : -- Add localization to vardef field_help
+        $fmd->help = $field->help_label;
+        //HACK :
         if (!empty($field->len))
             $fmd->len = $field->len; // tyoung bug 15407 - was being set to $field->size so changes weren't being saved
         $fmd->required = ($field->required ? 1 : 0);
Index: modules/DynamicFields/templates/Fields/Forms/coreTop.tpl
===================================================================
--- modules/DynamicFields/templates/Fields/Forms/coreTop.tpl (revision 1)
+++ modules/DynamicFields/templates/Fields/Forms/coreTop.tpl (working copy)
@@ -44,19 +44,22 @@
  <td class='mbLBL' width='30%' >{$MOD.COLUMN_TITLE_NAME}:</td>
  <td>
  {if $hideLevel == 0}
-  <input id="field_name_id" maxlength=30 type="text" name="name" value="{$vardef.name}" 
-    onchange="
-  document.getElementById('label_key_id').value = 'LBL_'+document.getElementById('field_name_id').value.toUpperCase();
+  {* HACK:  -- Add localization to vardef field_help 
+   Added javascript statement for automatic help label assignment *}
+  <input id="field_name_id" maxlength=30 type="text" name="name" value="{$vardef.name}" onchange="document.getElementById('label_key_id').value = 'LBL_'+document.getElementById('field_name_id').value.toUpperCase();
   document.getElementById('label_value_id').value = document.getElementById('field_name_id').value.replace(/_/,' ');
-  document.getElementById('field_name_id').value = document.getElementById('field_name_id').value.toLowerCase();" />
+  document.getElementById('field_name_id').value = document.getElementById('field_name_id').value.toLowerCase();  
+  document.getElementById('help_key_id').value = 'LBL_'+(document.getElementById('field_name_id').value.toUpperCase())+'_HELP';" />
+  {* HACK:  *}
  {else}
+  {* HACK:  -- Add localization to vardef field_help 
+   Added javascript statement for automatic help label assignment *}
   <input id= "field_name_id" maxlength=30 type="hidden" name="name" value="{$vardef.name}" 
-    onchange="
-  document.getElementById('label_key_id').value = 'LBL_'+document.getElementById('field_name_id').value.toUpperCase();
+    onchange="document.getElementById('label_key_id').value = 'LBL_'+document.getElementById('field_name_id').value.toUpperCase();
   document.getElementById('label_value_id').value = document.getElementById('field_name_id').value.replace(/_/,' ');
-  document.getElementById('field_name_id').value = document.getElementById('field_name_id').value.toLowerCase();"/>
-  {$vardef.name}
- {/if}
+  document.getElementById('field_name_id').value = document.getElementById('field_name_id').value.toLowerCase();
+  document.getElementById('help_key_id').value = 'LBL_'+(document.getElementById('field_name_id').value.toUpperCase())+'_HELP';"/>{$vardef.name}{/if}
+  {* HACK: *}
   <script>
   addToValidate('popup_form', 'name', 'DBName', true,'{$MOD.COLUMN_TITLE_NAME} [a-zA-Z_]' );
   addToValidateIsInArray('popup_form', 'name', 'in_array', true,'{$MOD.ERR_RESERVED_FIELD_NAME}', '{$field_name_exceptions}', 'u==');
@@ -79,11 +82,25 @@
  {/if}
  </td>
 </tr>
+{* HACK:  -- Add localization to vardef field_help ,Course module Accounting type does not work *}
 <tr>
- <td class='mbLBL'>{$MOD.COLUMN_TITLE_HELP_TEXT}:</td><td>{if $hideLevel < 5 }<input type="text" name="help" value="{$vardef.help}">{else}<input type="hidden" name="help" value="{$vardef.help}">{$vardef.help}{/if}
+ <td class='mbLBL'>{$MOD.COLUMN_TITLE_HELP_TEXT}:</td>
+ <td>
+  <input type="text" id="help_value_id" name="help" value="{$vardef.help}" onchange="if(document.getElementById('help_key_id').value == '') document.getElementById('help_key_id').value='LBL_'+(document.getElementById('field_name_id').value.toUpperCase())+'_HELP';">
  </td>
 </tr>
+
 <tr>
+ <td class='mbLBL'>{$MOD.COLUMN_TITLE_HELP_LABEL}:</td>
+ <td>{if $hideLevel < 5 }
+  <input type="text" id="help_key_id" name="help_label" value="{$vardef.help_label}">
+  {else}
+  <input type="hidden" id="help_key_id" name="help_label" value="{$vardef.help_label}">{$vardef.help_label}
+  {/if}
+ </td>
+</tr>
+{* HACK: *}
+<tr>
     <td class='mbLBL'>{$MOD.COLUMN_TITLE_COMMENT_TEXT}:</td><td>{if $hideLevel < 5 }<input type="text" name="comments" value="{$vardef.comments}">{else}<input type="hidden" name="comment" value="{$vardef.comment}">{$vardef.comment}{/if}
     </td>
 </tr>
Index: modules/DynamicFields/templates/Fields/TemplateField.php
===================================================================
--- modules/DynamicFields/templates/Fields/TemplateField.php (revision 1)
+++ modules/DynamicFields/templates/Fields/TemplateField.php (working copy)
@@ -67,6 +67,10 @@
  var $reportable = false;
  var $label_value = '';
  var $help = '';
+ //HACK : -- Add localization to vardef field_help
+ // for initialisation of bean value.
+ var $help_label ='';
+ //HACK :
 
  var $vardef_map = array(
   'name'=>'name',
@@ -95,6 +99,11 @@
      //'disable_num_format'=>'ext3',
      'ext3'=>'ext3',
   'label_value'=>'label_value',
+
+  //HACK :  -- Add localization to vardef field_help
+  //used for vardef mapping.
+  'help_label'=>'help_label',
+  //HACK :  
  );
  /*
   HTML FUNCTIONS
Index: modules/EditCustomFields/FieldsMetaData.php
===================================================================
--- modules/EditCustomFields/FieldsMetaData.php (revision 1)
+++ modules/EditCustomFields/FieldsMetaData.php (working copy)
@@ -60,6 +60,10 @@
    var $ext1;
    var $ext2;
    var $ext3;
+ //HACK : -- Add localization to vardef field_help
+   var $help;
+   var $help_label;
+   //HACK :
  var $audited;
     var $duplicate_merge;
     var $reportable;
@@ -81,6 +85,10 @@
   'ext1',
   'ext2',
   'ext3',
+     //HACK : -- Add localization to vardef field_help
+  'help',
+  'help_label', 
+     //HACK : 
   'audited',
   'massupdate',
         'duplicate_merge',
Index: modules/EditCustomFields/language/en_us.lang.php
===================================================================
--- modules/EditCustomFields/language/en_us.lang.php (revision 1)
+++ modules/EditCustomFields/language/en_us.lang.php (working copy)
@@ -92,4 +92,9 @@
  'LBL_DEPENDENT_TRIGGER'=>'Trigger',
  'LBL_BTN_EDIT_VISIBILITY'=>'Edit Visibility',
 );
+
+// HACK : -- Add localization to vardef field_help
+// label for the new help label 
+$mod_strings['COLUMN_TITLE_HELP_LABEL'] = 'Help Label';
+// HACK :
 ?>
Index: modules/EditCustomFields/vardefs.php
===================================================================
--- modules/EditCustomFields/vardefs.php (revision 1)
+++ modules/EditCustomFields/vardefs.php (working copy)
@@ -66,4 +66,8 @@
   array('name' => 'idx_meta_cm_del', 'type' => 'index', 'fields' => array('custom_module', 'deleted')),
  ),
 );
+// HACK : -- Add localization to vardef field_help 
+// Added for adding new field help label in module builder. 
+$dictionary['FieldsMetaData']['fields']['help_label']=array('name' =>'help_label' ,'type' =>'varchar','vname'=>'COLUMN_TITLE_LABEL', 'len'=>'255');
+// HACK : -- Add localization to vardef field_help 
 ?>
Index: modules/ModuleBuilder/controller.php
===================================================================
--- modules/ModuleBuilder/controller.php (revision 1)
+++ modules/ModuleBuilder/controller.php (working copy)
@@ -269,6 +269,14 @@
         if (! empty ( $_REQUEST [ 'view_module' ] ) && !empty($_REQUEST [ 'labelValue' ]))
         {
             $_REQUEST [ "label_" . $_REQUEST [ 'label' ] ] = $_REQUEST [ 'labelValue' ] ;
+   // HACK : -- Add localization to vardef field_help
+             if (isset($_REQUEST['help_label']) && isset ($_REQUEST['help' ])){
+    if(empty($_REQUEST['help_label'])){
+              $_REQUEST['help_label']="LBL_".$_REQUEST['name']."_HELP";
+                }
+    $_REQUEST [ "label_" . $_REQUEST['help_label'] ] = $_REQUEST [ 'help' ] ;
+             } 
+            // HACK :
             require_once 'modules/ModuleBuilder/parsers/parser.label.php' ;
             $parser = new ParserLabel ( $_REQUEST['view_module'] , isset ( $_REQUEST [ 'view_package' ] ) ? $_REQUEST [ 'view_package' ] : null ) ;
             $parser->handleSave ( $_REQUEST, $GLOBALS [ 'current_language' ] ) ;
@@ -332,11 +340,22 @@
             $mb = new ModuleBuilder ( ) ;
             $module = & $mb->getPackageModule ( $_REQUEST [ 'view_package' ], $_REQUEST [ 'view_module' ] ) ;
             $field->save ( $module ) ;
+            //HACK : -- Add localization to vardef field_help
+            if(isset($_REQUEST['help_label']) && empty($_REQUEST['help_label']))
+              $_REQUEST['help_label']="LBL_".mb_strtoupper($_REQUEST['name'])."_HELP";         
+           
+   $module->mbvardefs->vardef['fields'][$_REQUEST['name']]['help']=$_REQUEST['help_label'];
+            //HACK :
             $module->mbvardefs->save () ;
             // get the module again to refresh the labels we might have saved with the $field->save (e.g., for address fields)
             $module = & $mb->getPackageModule ( $_REQUEST [ 'view_package' ], $_REQUEST [ 'view_module' ] ) ;
             if (isset ( $_REQUEST [ 'label' ] ) && isset ( $_REQUEST [ 'labelValue' ] ))
                 $module->setLabel ( $GLOBALS [ 'current_language' ], $_REQUEST [ 'label' ], $_REQUEST [ 'labelValue' ] ) ;
+            //HACK : -- Add localization to vardef field_help
+            //setting the help text and label for current language.            
+            if (isset($_REQUEST['help_label']) && isset ($_REQUEST['help']))
+              $module->setLabel($GLOBALS['current_language'],$_REQUEST['help_label'],$_REQUEST['help']);         
+            //HACK :
             $module->save();
         }
         $this->view = 'modulefields' ;
Index: modules/ModuleBuilder/views/view.modulefield.php
===================================================================
--- modules/ModuleBuilder/views/view.modulefield.php (revision 1)
+++ modules/ModuleBuilder/views/view.modulefield.php (working copy)
@@ -145,6 +145,31 @@
             if(!empty($vardef['vname'])){
                 $fv->ss->assign('lbl_value', htmlentities(translate($vardef['vname'], $moduleName), ENT_QUOTES, 'UTF-8'));
             }
+            //HACK : -- Add localization to vardef field_help
+            $temp_var_help_translation='';
+            if(isset($vardef['help'])){
+             if(!empty($vardef['help'])){
+              $temp_var_help_translation=translate(mb_strtoupper($vardef['help']),$moduleName);
+              if($temp_var_help_translation != mb_strtoupper($vardef['help'])){
+      $vardef['help_label']=$vardef['help'];
+               $vardef['help']=$temp_var_help_translation;
+     }else{
+      $vardef['help_label']=$vardef['help'];
+               $vardef['help']='';      
+     }
+             }
+         }elseif(!empty($vardef['name'])){ 
+    $tempkey="LBL_".mb_strtoupper($vardef['name'])."_HELP";
+    $temp_var_help_translation = translate(mb_strtoupper($tempkey),$moduleName);
+             if($temp_var_help_translation != mb_strtoupper($tempkey)){
+     $vardef['help_label']=$tempkey;
+              $vardef['help']=$temp_var_help_translation;
+    }else{
+     $vardef['help_label']=$tempkey;
+              $vardef['help']='';          
+    }
+         }
+            //HACK :
             //$package = new stdClass;
             //$package->name = 'studio';
             //$fv->ss->assign('package', $package);
@@ -186,6 +211,35 @@
             $fv->ss->assign('module', $module);
             $fv->ss->assign('package', $package);
             $fv->ss->assign('MB','1');
+   //HACK : -- Add localization to vardef field_help
+            $temp_var_help_translation='';
+            if(isset($vardef['help'])){ 
+             if(!empty($vardef['help'])){
+     //HACK:Upgrade sugarbase to SugarCRM 5.5
+              $temp_var_help_translation=$module->getLabel($GLOBALS['current_language'],$vardef['help']);
+     //HACK:Upgrade sugarbase to SugarCRM 5.5
+              if(!empty($temp_var_help_translation)){
+      $vardef['help_label']=$vardef['help'];
+               $vardef['help']=$temp_var_help_translation;
+     }else{
+      $vardef['help_label']=$vardef['help'];
+               $vardef['help']='';      
+     }
+             }
+         }elseif(!empty($vardef['name'])){ 
+    $tempkey="LBL_".mb_strtoupper($vardef['name'])."_HELP";
+    //HACK:Upgrade sugarbase to SugarCRM 5.5
+    $temp_var_help_translation=$module->getLabel($GLOBALS['current_language'],$tempkey);
+    //HACK:Upgrade sugarbase to SugarCRM 5.5
+             if(!empty($temp_var_help_translation)){
+     $vardef['help_label']=$tempkey;
+              $vardef['help']=$temp_var_help_translation;
+    }else{
+     $vardef['help_label']=$tempkey;
+              $vardef['help']='';          
+    }
+         }
+            //HACK :
 
             if(isset($vardef['vname']))
                 $fv->ss->assign('lbl_value', htmlentities($module->getLabel('en_us',$vardef['vname']), ENT_QUOTES, 'UTF-8'));

SugarCrm 5.5.2 - Grouped menu : Allow hiding in subpanels or main menu

Tabgroup - Should be main menu, subpanel, or both

This feature will allow the user to hide a tab in main menu or subpanel tabs or in both. So user can customize it based on their requirement.
To use this feature go to Admin -> Configure Tabs Group as seen in the screen shot below



1. If Hide in top menu checkbox checked group will not be shown in top menu.
2. If Hide in subpanel checkbox checked group will not be shown in subpanel.
3. After setting the hide properties & label value click on the save button (floppy icon) and then press Save & Deploy button. Now the top menu tab & subpanel tab will reflect the changes just made.


This customization is non upgrade safe.
The following files have been modified in the 5.5.2 released code.

1. include/GroupedTabs/GroupedTabStructure.php
a. New argument $is_subpanel = false is added in function get_tab_structure. This function also modified to filter hidden entry.

// HACK: Tabgroup - Should be main menu, subpanel, or both
function get_tab_structure($modList = '', $patch = '', $ignoreSugarConfig=false, $labelAsKey=false, $is_subpanel = false)
// HACK: Tabgroup - Should be main menu, subpanel, or both
{
 global $modListHeader, $app_strings, $modInvisListActivities;
 
 /* Use default if not provided */
 if(!$modList)
 {
  $modList =& $modListHeader;
 }
 
 /* Apply patch, use a reference if we can */
 if($patch)
 {
  $tabStructure = $GLOBALS['tabStructure'];
  
  foreach($patch as $mainTab => $subModules)
  {
   $tabStructure[$mainTab]['modules'] = array_merge($tabStructure[$mainTab]['modules'], $subModules);
  }
 }
 else
 {
  $tabStructure =& $GLOBALS['tabStructure'];
 }
 
 $retStruct = array();
 $mlhUsed = array();
 //the invisible list should be merged if activities is set to be hidden
 if(in_array('Activities', $modList)){
  $modList = array_merge($modList,$modInvisListActivities);
 }
 
 //Add any iFrame tabs to the 'other' group.
 $moduleExtraMenu = array();
 if(!should_hide_iframes()) {
  $iFrame = new iFrame();
  $frames = $iFrame->lookup_frames('tab'); 
  foreach($frames as $key => $values){
   $moduleExtraMenu[$key] = $values;
  }
 } else if(isset($modList['iFrames'])) {
  unset($modList['iFrames']);
 }
   
 $modList = array_merge($modList,$moduleExtraMenu);
   
 /* Only return modules which exists in the modList */
 foreach($tabStructure as $mainTab => $subModules)
 {
  // HACK: Tabgroup - Should be main menu, subpanel, or both
  if($subModules['hideinsubpanel'] == true && $is_subpanel == true){
   continue;
  }
  // HACK: Tabgroup - Should be main menu, subpanel, or both

  //Ensure even empty groups are returned
  if($labelAsKey){
   $retStruct[$subModules['label']]['modules'] = array();
  }else{
   $retStruct[$app_strings[$subModules['label']]]['modules']= array();
  }
  
  foreach($subModules['modules'] as $key => $subModule)
  {
     /* Perform a case-insensitive in_array check
   * and mark whichever module matched as used.
   */ 
   foreach($modList as $module)
   {
    if(is_string($module) && strcasecmp($subModule, $module) === 0)
    {
     // HACK: Do not show tab groups marked as hideintopmenu=true in top menu
     if (!$subModules['hideintopmenu'] ){
      if($labelAsKey){
       $retStruct[$subModules['label']]['modules'][] = $subModule;
      } 
      else{
       $retStruct[$app_strings[$subModules['label']]]['modules'][] = $subModule;
      }
     }
     // HACK: Do not show tab groups marked as hideintopmenu=true in top menu

     // HACK : If subpanels then done hide grouped tabs
     if($is_subpanel === true){
     if($labelAsKey){
      $retStruct[$subModules['label']]['modules'][] = $subModule;
     }else{
      $retStruct[$app_strings[$subModules['label']]]['modules'][] = $subModule;
     }                        
     }                        
     // HACK : If subpanels then done hide grouped tabs
     
     $mlhUsed[$module] = true;
     break;
    }
    // HACK: subpanels are not grouped in tabs anymore, this fixes it
    elseif ((strpos($module, (strtolower($subModule) . '_')) !== false) || (strpos($module, ('_' . strtolower($subModule))) !== false)){
     // Make sure we do not make a subpanel tab for the focus module (main module) since this main module most likely is places in a tabgroup as well
     if(strcasecmp($subModule, $GLOBALS[focus]->object_name) !== 0) {
      // HACK: Upgrade sugarbase to SugarCRM 5.5
      if($labelAsKey){
       $retStruct[$subModules['label']]['modules'][$key] = $module;
      }
      else {
       $retStruct[$app_strings[$subModules['label']]]['modules'][$key] = $module;                        
      }
      // HACK: Upgrade sugarbase to SugarCRM 5.5
     }                        
     $mlhUsed[$module] = true;
     break;
    }
    // HACK: Subpanels are not grouped in tabs anymore, this fixes it
   }
  }
  //remove the group tabs if it has no sub modules under it         
  if($labelAsKey){
    if (empty($retStruct[$subModules['label']]['modules'])){
    unset($retStruct[$subModules['label']]);
    }
    }else{
    if (empty($retStruct[$app_strings[$subModules['label']]]['modules'])){
    unset($retStruct[$app_strings[$subModules['label']]]);
    }
  }
 }
 
   //The other tab is shown by default . If the other_group_tab_displayed was set false in sugar config, we will not show the 'Other' tab in Group Tab List.
 global $sugar_config;
 //If return result ignore sugar config or $sugar_config['other_group_tab_displayed'] is not set ever or $sugar_config['other_group_tab_displayed'] is true 
 if($ignoreSugarConfig || ( !isset($sugar_config['other_group_tab_displayed']) || $sugar_config['other_group_tab_displayed'])){
 /* Put all the unused modules in modList
  * into the 'Other' tab.
  */
  foreach($modList as $module)
  {
   if(is_array($module) || !isset($mlhUsed[$module]))
   {
     if($labelAsKey){
      $retStruct['LBL_TABGROUP_OTHER']['modules'] []= $module;
     }else{
      $retStruct[$app_strings['LBL_TABGROUP_OTHER']]['modules'] []= $module;
     }
   }
  }
 }else{
  //If  the 'Other' group tab was not allowed returned, we should check the $retStruct array to make sure there is no 'Other' group tab in it.
  if(isset($retStruct[$app_strings['LBL_TABGROUP_OTHER']])){
   if($labelAsKey){
    unset($retStruct['LBL_TABGROUP_OTHER']);
   }else{
    unset($retStruct[$app_strings['LBL_TABGROUP_OTHER']]);
   }
 
  }          
 }
//        _pp($retStruct);
 return $retStruct;
}


2. include/SubPanel/SubPanelTilesTabs.php
a. function getTabs modified to get filtered tab structure from GroupedTabStructure->get_tab_structure().

// HACK: Tabgroup - Should be main menu, subpanel, or both
$groupedTabsClass = new GroupedTabStructure();
$gtc = $groupedTabsClass->get_tab_structure($tabs, '', true, true, true);
 
foreach( $gtc as $mainTab => $subModules)
// HACK: Tabgroup - Should be main menu, subpanel, or both


3. modules/Studio/studiotabgroups.js
a. Function StudioTabGroup.prototype.editTabGroupLabel modified to show/hide checkboxes.

StudioTabGroup.prototype.editTabGroupLabel = function (id, done) {
 if (!done) {
  if (this.lastEditTabGroupLabel != -1) StudioTabGroup.prototype.editTabGroupLabel(this.lastEditTabGroupLabel, true);
  document.getElementById('tabname_' + id).style.display = 'none';
  document.getElementById('tablabel_' + id).style.display = '';
  // HACK: To allow us to decide if the tab group goes in top menu
        document.getElementById('tabTopMenuConfig_'+id).style.display = ''; // Show
        // HACK: To allow us to decide if the tab group goes in top menu
  document.getElementById('tabother_' + id).style.display = 'none';
  try {
   document.getElementById('tablabel_' + id).focus();
  }
  catch (er) {}
  this.lastEditTabGroupLabel = id;
  YAHOO.util.DragDropMgr.lock();
 } else {
  this.lastEditTabGroupLabel = -1;
  document.getElementById('tabname_' + id).innerHTML = document.getElementById('tablabel_' + id).value;
  document.getElementById('tabname_' + id).style.display = '';
  document.getElementById('tablabel_' + id).style.display = 'none';
        // HACK: To allow us to decide if the tab group goes in top menu
  if (document.getElementById('tabhideintopmenu_'+id).checked) {
            document.getElementById('slot'+id).style.fontStyle = 'italic;';
            document.getElementById('tabname_'+id).style.color = '#808080';
            document.getElementById('slot'+id).style.color = '#808080';
        }
        else {
            document.getElementById('slot'+id).style.fontStyle = 'normal';
            document.getElementById('tabname_'+id).style.fontStyle = 'normal;';
            document.getElementById('tabname_'+id).style.color = '#000000;';
            document.getElementById('slot'+id).style.color = '#000000;';
        }
        document.getElementById('tabTopMenuConfig_'+id).style.display = 'none'; // Hide
        // HACK: To allow us to decide if the tab group goes in top menu
  document.getElementById('tabother_' + id).style.display = '';
  YAHOO.util.DragDropMgr.unlock();
 }
}


4. modules/Studio/TabGroups/EditViewTabs.php
a. Group tab structure now used from $GLOBALS['tabStructure'].
//$groupedTabStructure = $groupedTabsClass->get_tab_structure('', '', true,true);
$groupedTabStructure = $GLOBALS['tabStructure'];


5. modules/Studio/TabGroups/EditViewTabs.tpl
a. Template modified to support 2 new hide property checkboxes. If for any tag group hide in top menu check box is checked then it will be shown grey & italic.
(the syntax highlighting in the code below is somewhat off due to the Smarty template)
<div id='slot{$tabCounter}' class='noBullet' {if $tab.hideintopmenu}style='color:#808080; font-style: italic;'{/if}>
<h2 id='handle{$tabCounter}' >
<span id='tabname_{$tabCounter}' class='slotB' {if $tab.hideintopmenu}style='color:#808080; font-style: italic;'{/if}>{$tab.labelValue}</span><br>
<span id='tabother_{$tabCounter}'>
<span onclick='studiotabs.editTabGroupLabel({$tabCounter}, false)'>{$editImage}</span>&nbsp;
{if $tab.label != $otherLabel }
 <span onclick='studiotabs.deleteTabGroup({$tabCounter})'>{$deleteImage}</span>
{/if}
</span></h2>
<input type='hidden' name='tablabelid_{$tabCounter}' id='tablabelid_{$tabCounter}'  value='{$tab.label}'><input type='text' name='tablabel_{$tabCounter}' id='tablabel_{$tabCounter}' style='display:none' value='{$tab.labelValue}' >
{* // HACK: Tabgroup - Should be main menu, subpanel, or both *}
<span id="tabTopMenuConfig_{$tabCounter}" name="tabTopMenuConfig_{$tabCounter}" style='display:none'>
<br/>
Hide in top menu:
<input type='checkbox' name='tabhideintopmenu_{$tabCounter}' id='tabhideintopmenu_{$tabCounter}' value='hide' {if $tab.hideintopmenu}checked{/if}>
<br/>
{* // HACK: Tabgroup - Should be main menu, subpanel, or both *}
Hide in subpanel:
<input type='checkbox' name='tabhideinsubpanel_{$tabCounter}' id='tabhideinsubpanel_{$tabCounter}' value='hide' {if $tab.hideinsubpanel}checked{/if}>
<br/>
{* // HACK: Tabgroup - Should be main menu, subpanel, or both *}

<img src="themes/default/images/icon_email_save.gif" border="0" onclick='studiotabs.editTabGroupLabel({$tabCounter}, true)'>
<br/><br/>
</span>
{* // HACK: Tabgroup - Should be main menu, subpanel, or both *}


6. modules/Studio/TabGroups/TabGroupHelper.php
a. function saveTabGroups modified to set both hide property in $tabGroups from $params, before saving $tabGroups to tabconfig file.

// HACK: Tabgroup - Should be main menu, subpanel, or both
if (!empty($params['tabhideintopmenu_' . $index])) {
 $tabGroups[$labelID]['hideintopmenu'] = true;
}
else {
 $tabGroups[$labelID]['hideintopmenu'] = false; 
}
// HACK: Tabgroup - Should be main menu, subpanel, or both

// HACK: Tabgroup - Should be main menu, subpanel, or both
if (!empty($params['tabhideinsubpanel_' . $index])) {
 $tabGroups[$labelID]['hideinsubpanel'] = true;
}
else {
 $tabGroups[$labelID]['hideinsubpanel'] = false; 
}
// HACK: Tabgroup - Should be main menu, subpanel, or both

And here it the SVN diff file

Index: include/GroupedTabs/GroupedTabStructure.php
===================================================================
--- include/GroupedTabs/GroupedTabStructure.php    (revision 1)
+++ include/GroupedTabs/GroupedTabStructure.php    (working copy)
@@ -53,7 +53,9 @@
      * 
      * @return  array   the complete tab-group structure
      */
-    function get_tab_structure($modList = '', $patch = '', $ignoreSugarConfig=false, $labelAsKey=false)
+    //Ketty: Tabgroup - Should be main menu, subpanel, or both
+    function get_tab_structure($modList = '', $patch = '', $ignoreSugarConfig=false, $labelAsKey=false, $is_subpanel = false)
+    //Ketty: Tabgroup - Should be main menu, subpanel, or both
     {
         global $modListHeader, $app_strings, $modInvisListActivities;
         
@@ -102,6 +104,12 @@
         /* Only return modules which exists in the modList */
         foreach($tabStructure as $mainTab => $subModules)
         {
+            //Hack  - Tabgroup - Should be main menu, subpanel, or both
+            if($subModules['hideinsubpanel'] == true && $is_subpanel == true){
+                continue;
+            }
+            //Hack  - Tabgroup - Should be main menu, subpanel, or both
+
             //Ensure even empty groups are returned
             if($labelAsKey){
                 $retStruct[$subModules['label']]['modules'] = array();
@@ -118,14 +126,47 @@
                 {
                     if(is_string($module) && strcasecmp($subModule, $module) === 0)
                     {
+                        // HACK  : Do not show tab groups marked as hideintopmenu=true in top menu
+                        if (!$subModules['hideintopmenu'] ){
+                            if($labelAsKey){
+                                $retStruct[$subModules['label']]['modules'][] = $subModule;
+                            } 
+                            else{
+                                $retStruct[$app_strings[$subModules['label']]]['modules'][] = $subModule;
+                            }
+                        }
+                        // HACK  : Do not show tab groups marked as hideintopmenu=true in top menu
+
+                        //HACK : if subpanels then done hide grouped tabs
+                        if($is_subpanel === true){
                         if($labelAsKey){
                             $retStruct[$subModules['label']]['modules'][] = $subModule;
                         }else{
                             $retStruct[$app_strings[$subModules['label']]]['modules'][] = $subModule;
                         }                        
+                        }                        
+                        //HACK : if subpanels then done hide grouped tabs
+                        
                         $mlhUsed[$module] = true;
                         break;
                     }
+                    // HACK  : subpanels are not grouped in tabs anymore, this fixes it
+                    elseif ((strpos($module, (strtolower($subModule) . '_')) !== false) || (strpos($module, ('_' . strtolower($subModule))) !== false)){
+                        // Make sure we do not make a subpanel tab for the focus module (main module) since this main module most likely is places in a tabgroup as well
+                        if(strcasecmp($subModule, $GLOBALS[focus]->object_name) !== 0) {
+                            //Ketty: Upgrade sugarbase to SugarCRM 5.5
+                            if($labelAsKey){
+                                $retStruct[$subModules['label']]['modules'][$key] = $module;
+                            }
+                            else {
+                                $retStruct[$app_strings[$subModules['label']]]['modules'][$key] = $module;                        
+                            }
+                            //Ketty: Upgrade sugarbase to SugarCRM 5.5
+                        }                        
+                        $mlhUsed[$module] = true;
+                        break;
+                    }
+                    // HACK  : subpanels are not grouped in tabs anymore, this fixes it
                 }
             }
             //remove the group tabs if it has no sub modules under it            
Index: include/SubPanel/SubPanelTilesTabs.php
===================================================================
--- include/SubPanel/SubPanelTilesTabs.php    (revision 1)
+++ include/SubPanel/SubPanelTilesTabs.php    (working copy)
@@ -125,7 +125,12 @@
         $groups =  array () ;
         $found = array () ;
 
-        foreach( $GLOBALS['tabStructure'] as $mainTab => $subModules)
+        //Ketty: Tabgroup - Should be main menu, subpanel, or both
+        $groupedTabsClass = new GroupedTabStructure();
+        $gtc = $groupedTabsClass->get_tab_structure($tabs, '', true, true, true);
+         
+        foreach( $gtc as $mainTab => $subModules)
+        //Ketty: Tabgroup - Should be main menu, subpanel, or both
         {
             foreach( $subModules['modules'] as $key => $subModule )
             {
Index: modules/Studio/studiotabgroups.js
===================================================================
--- modules/Studio/studiotabgroups.js    (revision 1)
+++ modules/Studio/studiotabgroups.js    (working copy)
@@ -32,15 +32,114 @@
  * technical reasons, the Appropriate Legal Notices must display the words
  * "Powered by SugarCRM".
  ********************************************************************************/
-var subtabCount=[];var subtabModules=[];var tabLabelToValue=[];StudioTabGroup=function(){this.lastEditTabGroupLabel=-1;};StudioTabGroup.prototype.editTabGroupLabel=function(id,done){if(!done){if(this.lastEditTabGroupLabel!=-1)StudioTabGroup.prototype.editTabGroupLabel(this.lastEditTabGroupLabel,true);document.getElementById('tabname_'+id).style.display='none';document.getElementById('tablabel_'+id).style.display='';document.getElementById('tabother_'+id).style.display='none';try{document.getElementById('tablabel_'+id).focus();}
-catch(er){}
-this.lastEditTabGroupLabel=id;YAHOO.util.DragDropMgr.lock();}else{this.lastEditTabGroupLabel=-1;document.getElementById('tabname_'+id).innerHTML=document.getElementById('tablabel_'+id).value;document.getElementById('tabname_'+id).style.display='';document.getElementById('tablabel_'+id).style.display='none';document.getElementById('tabother_'+id).style.display='';YAHOO.util.DragDropMgr.unlock();}}
-StudioTabGroup.prototype.generateForm=function(formname){var form=document.getElementById(formname);for(var j=0;j<slotCount;j++){var ul=document.getElementById('ul'+j);var items=ul.getElementsByTagName('li');for(var i=0;i<items.length;i++){if(typeof(subtabModules[items[i].id])!='undefined'){var input=document.createElement('input');input.type='hidden';input.name=j+'_'+i;input.value=tabLabelToValue[subtabModules[items[i].id]];form.appendChild(input);}}}
-form.slot_count.value=slotCount;};StudioTabGroup.prototype.generateGroupForm=function(formname){var form=document.getElementById(formname);for(j=0;j<slotCount;j++){var ul=document.getElementById('ul'+j);items=ul.getElementsByTagName('li');for(i=0;i<items.length;i++){if(typeof(subtabModules[items[i].id])!='undefined'){var input=document.createElement('input');input.type='hidden'
-input.name='group_'+j+'[]';input.value=tabLabelToValue[subtabModules[items[i].id]];form.appendChild(input);}}}};StudioTabGroup.prototype.deleteTabGroup=function(id){if(document.getElementById('delete_'+id).value==0){document.getElementById('ul'+id).style.display='none';document.getElementById('tabname_'+id).style.textDecoration='line-through'
-document.getElementById('delete_'+id).value=1;}else{document.getElementById('ul'+id).style.display='';document.getElementById('tabname_'+id).style.textDecoration='none'
-document.getElementById('delete_'+id).value=0;}}
-var lastField='';var lastRowCount=-1;var undoDeleteDropDown=function(transaction){deleteDropDownValue(transaction['row'],document.getElementById(transaction['id']),false);}
-jstransaction.register('deleteDropDown',undoDeleteDropDown,undoDeleteDropDown);function deleteDropDownValue(rowCount,field,record){if(record){jstransaction.record('deleteDropDown',{'row':rowCount,'id':field.id});}
-if(field.value=='0'){field.value='1';document.getElementById('slot'+rowCount+'_value').style.textDecoration='line-through';}else{field.value='0';document.getElementById('slot'+rowCount+'_value').style.textDecoration='none';}}
-var studiotabs=new StudioTabGroup();
\ No newline at end of file
+var subtabCount = [];
+var subtabModules = [];
+var tabLabelToValue = [];
+StudioTabGroup = function () {
+    this.lastEditTabGroupLabel = -1;
+};
+StudioTabGroup.prototype.editTabGroupLabel = function (id, done) {
+    if (!done) {
+        if (this.lastEditTabGroupLabel != -1) StudioTabGroup.prototype.editTabGroupLabel(this.lastEditTabGroupLabel, true);
+        document.getElementById('tabname_' + id).style.display = 'none';
+        document.getElementById('tablabel_' + id).style.display = '';
+        // HACK Kenenth , to allow us to decide if the tab group goes in top menu
+        document.getElementById('tabTopMenuConfig_'+id).style.display = ''; // Show
+        // HACK Kenenth , to allow us to decide if the tab group goes in top menu
+        document.getElementById('tabother_' + id).style.display = 'none';
+        try {
+            document.getElementById('tablabel_' + id).focus();
+        }
+        catch (er) {}
+        this.lastEditTabGroupLabel = id;
+        YAHOO.util.DragDropMgr.lock();
+    } else {
+        this.lastEditTabGroupLabel = -1;
+        document.getElementById('tabname_' + id).innerHTML = document.getElementById('tablabel_' + id).value;
+        document.getElementById('tabname_' + id).style.display = '';
+        document.getElementById('tablabel_' + id).style.display = 'none';
+        // HACK Kenenth , to allow us to decide if the tab group goes in top menu
+        if (document.getElementById('tabhideintopmenu_'+id).checked) {
+            document.getElementById('slot'+id).style.fontStyle = 'italic;';
+            document.getElementById('tabname_'+id).style.color = '#808080';
+            document.getElementById('slot'+id).style.color = '#808080';
+        }
+        else {
+            document.getElementById('slot'+id).style.fontStyle = 'normal';
+            document.getElementById('tabname_'+id).style.fontStyle = 'normal;';
+            document.getElementById('tabname_'+id).style.color = '#000000;';
+            document.getElementById('slot'+id).style.color = '#000000;';
+        }
+        document.getElementById('tabTopMenuConfig_'+id).style.display = 'none'; // Hide
+        // HACK Kenenth , to allow us to decide if the tab group goes in top menu
+        document.getElementById('tabother_' + id).style.display = '';
+        YAHOO.util.DragDropMgr.unlock();
+    }
+}
+StudioTabGroup.prototype.generateForm = function (formname) {
+    var form = document.getElementById(formname);
+    for (var j = 0; j < slotCount; j++) {
+        var ul = document.getElementById('ul' + j);
+        var items = ul.getElementsByTagName('li');
+        for (var i = 0; i < items.length; i++) {
+            if (typeof(subtabModules[items[i].id]) != 'undefined') {
+                var input = document.createElement('input');
+                input.type = 'hidden';
+                input.name = j + '_' + i;
+                input.value = tabLabelToValue[subtabModules[items[i].id]];
+                form.appendChild(input);
+            }
+        }
+    }
+    form.slot_count.value = slotCount;
+};
+StudioTabGroup.prototype.generateGroupForm = function (formname) {
+    var form = document.getElementById(formname);
+    for (j = 0; j < slotCount; j++) {
+        var ul = document.getElementById('ul' + j);
+        items = ul.getElementsByTagName('li');
+        for (i = 0; i < items.length; i++) {
+            if (typeof(subtabModules[items[i].id]) != 'undefined') {
+                var input = document.createElement('input');
+                input.type = 'hidden'
+                input.name = 'group_' + j + '[]';
+                input.value = tabLabelToValue[subtabModules[items[i].id]];
+                form.appendChild(input);
+            }
+        }
+    }
+};
+StudioTabGroup.prototype.deleteTabGroup = function (id) {
+    if (document.getElementById('delete_' + id).value == 0) {
+        document.getElementById('ul' + id).style.display = 'none';
+        document.getElementById('tabname_' + id).style.textDecoration = 'line-through'
+        document.getElementById('delete_' + id).value = 1;
+    } else {
+        document.getElementById('ul' + id).style.display = '';
+        document.getElementById('tabname_' + id).style.textDecoration = 'none'
+        document.getElementById('delete_' + id).value = 0;
+    }
+}
+var lastField = '';
+var lastRowCount = -1;
+var undoDeleteDropDown = function (transaction) {
+    deleteDropDownValue(transaction['row'], document.getElementById(transaction['id']), false);
+}
+jstransaction.register('deleteDropDown', undoDeleteDropDown, undoDeleteDropDown);
+
+function deleteDropDownValue(rowCount, field, record) {
+    if (record) {
+        jstransaction.record('deleteDropDown', {
+            'row': rowCount,
+            'id': field.id
+        });
+    }
+    if (field.value == '0') {
+        field.value = '1';
+        document.getElementById('slot' + rowCount + '_value').style.textDecoration = 'line-through';
+    } else {
+        field.value = '0';
+        document.getElementById('slot' + rowCount + '_value').style.textDecoration = 'none';
+    }
+}
+var studiotabs = new StudioTabGroup();
\ No newline at end of file
Index: modules/Studio/TabGroups/EditViewTabs.php
===================================================================
--- modules/Studio/TabGroups/EditViewTabs.php    (revision 1)
+++ modules/Studio/TabGroups/EditViewTabs.php    (working copy)
@@ -54,7 +54,8 @@
 $selectedAppLanguages = return_application_language($tabGroupSelected_lang);
 require_once('include/GroupedTabs/GroupedTabStructure.php');
 $groupedTabsClass = new GroupedTabStructure();
-$groupedTabStructure = $groupedTabsClass->get_tab_structure('', '', true,true);
+//$groupedTabStructure = $groupedTabsClass->get_tab_structure('', '', true,true);
+$groupedTabStructure = $GLOBALS['tabStructure'];
 foreach($groupedTabStructure as $mainTab => $subModules){
      $groupedTabStructure[$mainTab]['label'] = $mainTab;
      $groupedTabStructure[$mainTab]['labelValue'] = $selectedAppLanguages[$mainTab];
Index: modules/Studio/TabGroups/EditViewTabs.tpl
===================================================================
--- modules/Studio/TabGroups/EditViewTabs.tpl    (revision 1)
+++ modules/Studio/TabGroups/EditViewTabs.tpl    (working copy)
@@ -157,11 +157,32 @@
 </tr><tr>
 {/if}
 <td valign='top' class='tdContainer'>
-<div id='slot{$tabCounter}' class='noBullet'><h2 id='handle{$tabCounter}' ><span id='tabname_{$tabCounter}' class='slotB'>{$tab.labelValue}</span><br><span id='tabother_{$tabCounter}'><span onclick='studiotabs.editTabGroupLabel({$tabCounter}, false)'>{$editImage}</span>&nbsp;
+<div id='slot{$tabCounter}' class='noBullet' {if $tab.hideintopmenu}style='color:#808080; font-style: italic;'{/if}>
+<h2 id='handle{$tabCounter}' >
+<span id='tabname_{$tabCounter}' class='slotB' {if $tab.hideintopmenu}style='color:#808080; font-style: italic;'{/if}>{$tab.labelValue}</span><br>
+<span id='tabother_{$tabCounter}'>
+<span onclick='studiotabs.editTabGroupLabel({$tabCounter}, false)'>{$editImage}</span>&nbsp;
 {if $tab.label != $otherLabel }
     <span onclick='studiotabs.deleteTabGroup({$tabCounter})'>{$deleteImage}</span>
 {/if}
-</span></h2><input type='hidden' name='tablabelid_{$tabCounter}' id='tablabelid_{$tabCounter}'  value='{$tab.label}'><input type='text' name='tablabel_{$tabCounter}' id='tablabel_{$tabCounter}' style='display:none' value='{$tab.labelValue}' onblur='studiotabs.editTabGroupLabel({$tabCounter}, true)'>
+</span></h2>
+<input type='hidden' name='tablabelid_{$tabCounter}' id='tablabelid_{$tabCounter}'  value='{$tab.label}'><input type='text' name='tablabel_{$tabCounter}' id='tablabel_{$tabCounter}' style='display:none' value='{$tab.labelValue}' >
+{* HACK   to allow us to indicate if a TAB GROUP is only to group subpanels and thus not is to be shown in the topmenu *}
+<span id="tabTopMenuConfig_{$tabCounter}" name="tabTopMenuConfig_{$tabCounter}" style='display:none'>
+<br/>
+Hide in top menu:
+<input type='checkbox' name='tabhideintopmenu_{$tabCounter}' id='tabhideintopmenu_{$tabCounter}' value='hide' {if $tab.hideintopmenu}checked{/if}>
+<br/>
+{* //Hack  - Tabgroup - Should be main menu, subpanel, or both *}
+Hide in subpanel:
+<input type='checkbox' name='tabhideinsubpanel_{$tabCounter}' id='tabhideinsubpanel_{$tabCounter}' value='hide' {if $tab.hideinsubpanel}checked{/if}>
+<br/>
+{* //Hack  - Tabgroup - Should be main menu, subpanel, or both *}
+
+<img src="themes/default/images/icon_email_save.gif" border="0" onclick='studiotabs.editTabGroupLabel({$tabCounter}, true)'>
+<br/><br/>
+</span>
+{* HACK   to allow us to indicate if a TAB GROUP is only to group subpanels and thus not is to be shown in the topmenu *}
 <ul id='ul{$tabCounter}' class='listContainer'>
 {counter start=0 name="subtabCounter" print=false assign="subtabCounter"}
 {foreach from=$tab.modules item='list'}
Index: modules/Studio/TabGroups/TabGroupHelper.php
===================================================================
--- modules/Studio/TabGroups/TabGroupHelper.php    (revision 1)
+++ modules/Studio/TabGroups/TabGroupHelper.php    (working copy)
@@ -123,6 +123,24 @@
                 
             }
             $tabGroups[$labelID] = array('label'=>$labelID);
+
+            // HACK   to allow supanel groupings to not be shown in top menu
+            if (!empty($params['tabhideintopmenu_' . $index])) {
+                $tabGroups[$labelID]['hideintopmenu'] = true;
+            }
+            else {
+                $tabGroups[$labelID]['hideintopmenu'] = false; 
+            }
+            // HACK   to allow supanel groupings to not be shown in top menu
+            
+            //Hack  - Tabgroup - Should be main menu, subpanel, or both
+            if (!empty($params['tabhideinsubpanel_' . $index])) {
+                $tabGroups[$labelID]['hideinsubpanel'] = true;
+            }
+            else {
+                $tabGroups[$labelID]['hideinsubpanel'] = false; 
+            }
+            //Hack  - Tabgroup - Should be main menu, subpanel, or both
             $tabGroups[$labelID]['modules']= array();
             for($subcount = 0; isset($params[$index.'_' . $subcount]); $subcount++){
                 $tabGroups[$labelID]['modules'][] = $params[$index.'_' . $subcount];

Droidulus - Reverse Calculator for Android Desire (very much beta)

I have over time slowly improved this reverse calculator for 2 reasons. 

  1. My daughter likes to try my phone
  2. As long as she find it fun I might as well try to get her used to the multiplication and math.
Here is a small video showing the application




For those interested you can find the app here Droidulus - Reverse Calculator.

There are many things that could be improved



I have posted the source code on Github more specifically located here kenneththorman's Droidulus at master - GitHub.