InputText: Fixed multi-threading issues

This commit is contained in:
spx 2013-03-02 00:29:25 +09:00
parent 76e504d1e5
commit 167123bcf7
3 changed files with 210 additions and 113 deletions

View File

@ -294,6 +294,9 @@ namespace InputText
public bool ShowInputBox()
{
if (this.drBackup != DialogResult.None)
return false;
if (this._FocusDismiss)
{
this.Show(this.parent);
@ -324,6 +327,12 @@ namespace InputText
return true;
}
public void Abort()
{
this.drBackup = DialogResult.Cancel;
this.Close();
}
private void txtInput_Leave(object sender, EventArgs e)
{
if (this._FocusDismiss)

View File

@ -71,17 +71,51 @@ namespace InputText
if (go)
{
ThreadPool.QueueUserWorkItem(ExecuteBangThread, args);
ExecuteBangParam param = new ExecuteBangParam(args);
if (ReadOptions(param)) // Read all options in advance for thread-safety
{
ThreadPool.QueueUserWorkItem(ExecuteBangThread, param);
}
else
{
// No need to continue
lock (this.locker)
{
this.IsExecuteBangRunning = false;
}
}
}
}
internal class ExecuteBangParam
{
internal enum BangType
{
Unknown,
SetVariable,
ExecuteBatch
};
internal Dictionary<string, string> Options;
internal List<string> Commands;
internal string Command;
internal BangType Type;
internal ExecuteBangParam(string args)
{
this.Options = new Dictionary<string, string>();
this.Commands = new List<string>();
this.Command = args.Trim();
this.Type = BangType.Unknown;
}
};
private void ExecuteBangThread(object state)
{
string command = (string)state;
ExecuteBangParam param = (ExecuteBangParam)state;
try
{
ExecuteCommands(command);
ExecuteCommands(param);
}
catch (Exception ex)
{
@ -93,6 +127,11 @@ namespace InputText
this.IsExecuteBangRunning = false;
}
}
internal void Dispose()
{
CloseInputBox();
}
}
#region Plugin
@ -112,6 +151,7 @@ namespace InputText
public unsafe static void Finalize(void* data)
{
uint id = (uint)data;
Measures[id].Dispose();
Measures.Remove(id);
}

View File

@ -25,59 +25,45 @@ namespace InputText
{
internal partial class Measure
{
private void ExecuteCommands(string Command)
private bool ReadOptions(ExecuteBangParam param)
{
Command = Command.Trim();
// Get default options
Dictionary<string, string> Options = new Dictionary<string, string>();
ReadOption("DefaultValue", Options);
ReadOption("X", Options);
ReadOption("Y", Options);
ReadOption("W", Options);
ReadOption("H", Options);
ReadOption("StringStyle", Options);
ReadOption("StringAlign", Options);
ReadOption("FocusDismiss", Options);
ReadOption("FontColor", Options);
ReadOption("FontFace", Options);
ReadOption("FontSize", Options);
ReadOption("SolidColor", Options);
ReadOption("Password", Options);
ReadOption("TopMost", Options);
ReadOption("DefaultValue", param.Options);
ReadOption("X", param.Options);
ReadOption("Y", param.Options);
ReadOption("W", param.Options);
ReadOption("H", param.Options);
ReadOption("StringStyle", param.Options);
ReadOption("StringAlign", param.Options);
ReadOption("FocusDismiss", param.Options);
ReadOption("FontColor", param.Options);
ReadOption("FontFace", param.Options);
ReadOption("FontSize", param.Options);
ReadOption("SolidColor", param.Options);
ReadOption("Password", param.Options);
ReadOption("TopMost", param.Options);
#region Handle a single parameter
// If our parameter list only contains a single word, then open a textbox immediately
// and set a value. This mode does not do any batching.
if (!Command.Contains(" "))
if (!param.Command.Contains(" "))
{
// Assume that the parameter is the name of the variable
// Ask for input
string sInput = GetUserInput(Options);
// If the user cancelled out of the inputbox (ESC key, etc.), then abort
if (sInput == null)
return;
// Ask Rainmeter to set the variable using a bang (http://rainmeter.net/RainCMS/?q=Bangs)
API.Execute(rm.GetSkin(), "!SetVariable " + Command + " \"" + sInput + "\"");
// Note that the skin needs DynamicVariables=1 in the measure's settings or the above
// code will have no effect.
return;
param.Type = ExecuteBangParam.BangType.SetVariable;
return true;
}
#endregion
#region Handle multiple parameters
// Our parameter list contains at least two words, so split them up
string[] sParts = Command.Split(new string[] { " " }, StringSplitOptions.None);
string[] sParts = param.Command.Split(new string[] { " " }, StringSplitOptions.None);
// If the first parameter is 'ExecuteBatch' (not case sensitive)...
if (sParts[0].Trim().ToUpper() == "EXECUTEBATCH")
{
param.Type = ExecuteBangParam.BangType.ExecuteBatch;
// ExecuteBatch tells this plugin to go through the measure's settings to look
// for lines beginning with "Command" and executing Rainmeter bangs for each one.
// If a line contains $UserInput$, then an input textbox is opened and command
@ -131,8 +117,6 @@ namespace InputText
#region Parse commands in range
// Parse each command in the range, aborting if any line returns 'false' or
// the requested command line does not exist in the config for that measure.
List<string> commands = new List<string>();
for (int i = iMin; i <= iMax; i++)
{
// Read this command's line
@ -142,28 +126,59 @@ namespace InputText
if (string.IsNullOrEmpty(sCurrentLine))
break;
commands.Add(sCurrentLine);
}
foreach (string sCurrentLine in commands)
{
// Execute the line, but if there's a problem (error or they cancel the
// input textbox), then abort
if (!ExecuteLine(sCurrentLine, Options))
break;
// Continue to the next line, if there is any
param.Commands.Add(sCurrentLine);
}
#endregion
return;
}
// Unhandled command, log the message but otherwise do nothing
API.Log(API.LogType.Debug, "InputText: Received command \"" + sParts[0].Trim() + "\", left unhandled");
return param.Commands.Count > 0;
}
#endregion
return;
// Unhandled command, log the message but otherwise do nothing
param.Type = ExecuteBangParam.BangType.Unknown;
API.Log(API.LogType.Debug, "InputText: Received command \"" + sParts[0].Trim() + "\", left unhandled");
return false;
}
private void ExecuteCommands(ExecuteBangParam param)
{
switch (param.Type)
{
case ExecuteBangParam.BangType.SetVariable:
{
// Assume that the parameter is the name of the variable
// Ask for input
string sInput = GetUserInput(param.Options);
// If the user cancelled out of the inputbox (ESC key, etc.), then abort
if (sInput == null)
break;
// Ask Rainmeter to set the variable using a bang (http://rainmeter.net/RainCMS/?q=Bangs)
API.Execute(rm.GetSkin(), "!SetVariable " + param.Command + " \"" + sInput + "\"");
// Note that the skin needs DynamicVariables=1 in the measure's settings or the above
// code will have no effect.
}
break;
case ExecuteBangParam.BangType.ExecuteBatch:
{
foreach (string sCurrentLine in param.Commands)
{
// Execute the line, but if there's a problem (error or they cancel the
// input textbox), then abort
if (!ExecuteLine(sCurrentLine, param.Options))
break;
// Continue to the next line, if there is any
}
}
break;
}
}
#region This is all code custom to this plugin
@ -235,6 +250,9 @@ namespace InputText
delegate void ChangeSettingFromString(string value);
delegate void ChangeInputBoxSetting(string option, ChangeSettingFromString method);
private InputBox _InputBox = null;
private object _InputBoxLocker = new object();
private string GetUserInput(Dictionary<string, string> Options)
{
// No INI overrides provided, so create an empty list
@ -242,78 +260,108 @@ namespace InputText
}
private string GetUserInput(Dictionary<string, string> Options, Dictionary<string, string> Overrides)
{
SkinWindow skin = new SkinWindow(rm);
InputBox input = null;
// Create the form. 'InputBox' is a .NET form with a textbox and two button controls on it.
InputBox input = new InputBox(skin);
input.ChangeX("0");
input.ChangeY("0");
// Change the styles of the InputBox form based on overrides or INI values
#region Style and preference tweaks (INI and override settings)
ChangeInputBoxSetting changeSetting = (opt, change) =>
lock (this._InputBoxLocker)
{
if (Overrides.ContainsKey(opt))
change(Overrides[opt]);
else if (Options.ContainsKey(opt))
change(Options[opt]);
};
SkinWindow skin = new SkinWindow(rm);
changeSetting("FontFace", input.ChangeFontFace);
changeSetting("FontSize", input.ChangeFontSize);
// Create the form. 'InputBox' is a .NET form with a textbox and two button controls on it.
this._InputBox = new InputBox(skin);
input = this._InputBox;
changeSetting("W", input.ChangeW);
changeSetting("H", input.ChangeH);
changeSetting("X", input.ChangeX);
changeSetting("Y", input.ChangeY);
input.ChangeX("0");
input.ChangeY("0");
changeSetting("StringStyle", input.ChangeFontStringStyle);
changeSetting("StringAlign", input.ChangeStringAlign);
// Change the styles of the InputBox form based on overrides or INI values
#region Style and preference tweaks (INI and override settings)
changeSetting("FontColor", input.ChangeFontColor);
changeSetting("SolidColor", input.ChangeBackColor);
ChangeInputBoxSetting changeSetting = (opt, change) =>
{
if (Overrides.ContainsKey(opt))
change(Overrides[opt]);
else if (Options.ContainsKey(opt))
change(Options[opt]);
};
if (Overrides.ContainsKey("FocusDismiss"))
input.MakeFocusDismiss(Overrides["FocusDismiss"] == "1");
else if (Options.ContainsKey("FocusDismiss"))
input.MakeFocusDismiss(Options["FocusDismiss"].Trim() == "1");
changeSetting("FontFace", input.ChangeFontFace);
changeSetting("FontSize", input.ChangeFontSize);
if (Overrides.ContainsKey("Password"))
input.MakePassword(Overrides["Password"] == "1");
else if (Options.ContainsKey("Password"))
input.MakePassword(Options["Password"].Trim() == "1");
changeSetting("W", input.ChangeW);
changeSetting("H", input.ChangeH);
changeSetting("X", input.ChangeX);
changeSetting("Y", input.ChangeY);
string topmost = null;
if (Overrides.ContainsKey("TopMost"))
topmost = Overrides["TopMost"];
else if (Options.ContainsKey("TopMost"))
topmost = Options["TopMost"].Trim();
switch (topmost)
{
case "1":
input.MakeTopmost();
break;
case "0":
break;
default: // AUTO
if (skin.IsTopmost)
changeSetting("StringStyle", input.ChangeFontStringStyle);
changeSetting("StringAlign", input.ChangeStringAlign);
changeSetting("FontColor", input.ChangeFontColor);
changeSetting("SolidColor", input.ChangeBackColor);
if (Overrides.ContainsKey("FocusDismiss"))
input.MakeFocusDismiss(Overrides["FocusDismiss"] == "1");
else if (Options.ContainsKey("FocusDismiss"))
input.MakeFocusDismiss(Options["FocusDismiss"].Trim() == "1");
if (Overrides.ContainsKey("Password"))
input.MakePassword(Overrides["Password"] == "1");
else if (Options.ContainsKey("Password"))
input.MakePassword(Options["Password"].Trim() == "1");
string topmost = null;
if (Overrides.ContainsKey("TopMost"))
topmost = Overrides["TopMost"];
else if (Options.ContainsKey("TopMost"))
topmost = Options["TopMost"].Trim();
switch (topmost)
{
case "1":
input.MakeTopmost();
break;
break;
case "0":
break;
default: // AUTO
if (skin.IsTopmost)
input.MakeTopmost();
break;
}
changeSetting("DefaultValue", input.DefaultValue);
#endregion
}
changeSetting("DefaultValue", input.DefaultValue);
string result = null;
#endregion
if (!input.ShowInputBox())
return null;
lock (this.locker)
if (input.ShowInputBox())
{
this.LastInput = input.TextValue;
lock (this.locker)
{
this.LastInput = input.TextValue;
result = this.LastInput;
}
}
// Dispose
input = null;
lock (this._InputBoxLocker)
{
this._InputBox.Dispose();
this._InputBox = null;
}
return result;
}
private void CloseInputBox()
{
lock (this._InputBoxLocker)
{
if (this._InputBox != null)
{
this._InputBox.Abort();
System.Threading.Thread.Sleep(50); // Wait for closing input box
}
}
return input.TextValue;
}
#endregion