Factor out the placeholder text behavior from SubsEditBox and make it work better

Handle switching from placeholder/normal mode when the value is changed
externally (such as from the active line changing) in addition to on
focus/blur, and improve behavior when the user sets the text to the
placeholder text.

Originally committed to SVN as r6321.
This commit is contained in:
Thomas Goyne 2012-01-20 05:14:50 +00:00
parent 1ce9b0d31b
commit 4675dbb29d
4 changed files with 124 additions and 49 deletions

View file

@ -633,6 +633,10 @@
RelativePath="..\..\src\help_button.h"
>
</File>
<File
RelativePath="..\..\src\placeholder_ctrl.h"
>
</File>
<File
RelativePath="..\..\src\scintilla_text_ctrl.cpp"
>

View file

@ -0,0 +1,104 @@
// Copyright (c) 2012, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
//
// $Id$
/// @file placeholder_ctrl.h
/// @ingroup custom_control
///
#ifndef AGI_PRE
#include <wx/settings.h>
#endif
/// @class Placeholder
/// @brief A wrapper around a control to add placeholder text
///
/// This control wraps a base control to add default greyed-out placeholder
/// text describing the control when the value would otherwise be empty, which
/// is removed when the control is focused to begin typing in it, and restored
/// when the control loses focus and the value is empty
template<class BaseCtrl>
class Placeholder : public BaseCtrl {
wxString placeholder; ///< Placeholder string
bool is_placeholder; ///< Should the value be cleared on focus?
/// Wrapper around Create to make it possible to override it for specific
/// base classes
inline void Create(wxWindow *parent, wxSize const& size, long style) {
BaseCtrl::Create(parent, -1, placeholder, wxDefaultPosition, size, style);
}
/// Focus gained event handler
void OnSetFocus(wxFocusEvent& evt) {
evt.Skip();
if (is_placeholder) {
BaseCtrl::ChangeValue("");
BaseCtrl::SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
}
}
/// Focus lost event handler
void OnKillFocus(wxFocusEvent& evt) {
evt.Skip();
ChangeValue(BaseCtrl::GetValue());
}
public:
/// Constructor
/// @param parent Parent window
/// @param placeholder Placeholder string
/// @param size Control size
/// @param style Style flags to pass to the base control
/// @param tooltip Tooltip string
Placeholder(wxWindow *parent, wxString const& placeholder, wxSize const& size, long style, wxString const& tooltip)
: placeholder(placeholder)
, is_placeholder(true)
{
Create(parent, size, style);
BaseCtrl::SetToolTip(tooltip);
BaseCtrl::SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT));
BaseCtrl::Bind(wxEVT_SET_FOCUS, &Placeholder::OnSetFocus, this);
BaseCtrl::Bind(wxEVT_KILL_FOCUS, &Placeholder::OnKillFocus, this);
}
/// @brief Change the value of the control without triggering events
/// @param new_value New value of the control
///
/// If new_value is empty, the control will switch to placeholder mode
void ChangeValue(wxString new_value) {
if (new_value.empty()) {
is_placeholder = true;
new_value = placeholder;
BaseCtrl::SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT));
}
else {
is_placeholder = false;
BaseCtrl::SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
}
// This check should be pointless, but wxGTK is awesome and generates
// change events in wxComboBox::ChangeValue
if (new_value != BaseCtrl::GetValue())
BaseCtrl::ChangeValue(new_value);
}
};
template<> inline void Placeholder<wxComboBox>::Create(wxWindow *parent, wxSize const& size, long style) {
wxComboBox::Create(parent, -1, "", wxDefaultPosition, size, 0, 0, style);
}

View file

@ -63,6 +63,7 @@
#include "include/aegisub/hotkey.h"
#include "libresrc/libresrc.h"
#include "main.h"
#include "placeholder_ctrl.h"
#include "subs_edit_ctrl.h"
#include "subs_grid.h"
#include "timeedit_ctrl.h"
@ -113,34 +114,6 @@ static T get_value(AssDialogue const& line, int blockn, T initial, wxString tag,
return initial;
}
template<class Control>
struct FocusHandler : std::unary_function<wxFocusEvent &, void> {
wxString value;
wxString alt;
wxColor color;
Control *control;
void operator()(wxFocusEvent &event) const {
event.Skip();
if (control->GetValue() == alt) {
control->Freeze();
control->ChangeValue(value);
control->SetForegroundColour(color);
control->Thaw();
}
}
};
template<class Event, class T>
void bind_focus_handler(T *control, Event event, wxString value, wxString alt, wxColor color) {
FocusHandler<T> handler;
handler.value = value;
handler.alt = alt;
handler.color = color;
handler.control = control;
control->Bind(event, handler);
}
/// Get the block index in the text of the position
int block_at_pos(wxString const& text, int pos) {
int n = 0;
@ -182,10 +155,14 @@ SubsEditBox::SubsEditBox(wxWindow *parent, agi::Context *context)
TopSizer->Add(CommentBox, 0, wxRIGHT | wxALIGN_CENTER, 5);
StyleBox = MakeComboBox("Default", wxCB_READONLY, &SubsEditBox::OnStyleChange, _("Style for this line."));
ActorBox = MakeComboBox("Actor", wxCB_DROPDOWN, &SubsEditBox::OnActorChange, _("Actor name for this speech. This is only for reference, and is mainly useless."));
Effect = new wxTextCtrl(this,-1,"",wxDefaultPosition,wxSize(80,-1),wxTE_PROCESS_ENTER);
Effect->SetToolTip(_("Effect for this line. This can be used to store extra information for karaoke scripts, or for the effects supported by the renderer."));
ActorBox = new Placeholder<wxComboBox>(this, "Actor", wxSize(110, -1), wxCB_DROPDOWN | wxTE_PROCESS_ENTER, _("Actor name for this speech. This is only for reference, and is mainly useless."));
Bind(wxEVT_COMMAND_TEXT_UPDATED, &SubsEditBox::OnActorChange, this, ActorBox->GetId());
Bind(wxEVT_COMMAND_COMBOBOX_SELECTED, &SubsEditBox::OnActorChange, this, ActorBox->GetId());
TopSizer->Add(ActorBox, wxSizerFlags(2).Center().Border(wxRIGHT));
Effect = new Placeholder<wxTextCtrl>(this, "Effect", wxSize(80,-1), wxTE_PROCESS_ENTER, _("Effect for this line. This can be used to store extra information for karaoke scripts, or for the effects supported by the renderer."));
Bind(wxEVT_COMMAND_TEXT_UPDATED, &SubsEditBox::OnEffectChange, this, Effect->GetId());
TopSizer->Add(Effect, 3, wxALIGN_CENTER, 5);
// Middle controls
@ -243,24 +220,11 @@ SubsEditBox::SubsEditBox(wxWindow *parent, agi::Context *context)
SetSizerAndFit(MainSizer);
wxColour text = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
wxColour grey = wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT);
// Setup placeholders for effect and actor boxes
bind_focus_handler(Effect, wxEVT_SET_FOCUS, "", "Effect", text);
bind_focus_handler(Effect, wxEVT_KILL_FOCUS, "Effect", "", grey);
Effect->SetForegroundColour(grey);
bind_focus_handler(ActorBox, wxEVT_SET_FOCUS, "", "Actor", text);
bind_focus_handler(ActorBox, wxEVT_KILL_FOCUS, "Actor", "", grey);
ActorBox->SetForegroundColour(grey);
TextEdit->Bind(wxEVT_STC_MODIFIED, &SubsEditBox::OnChange, this);
TextEdit->SetModEventMask(wxSTC_MOD_INSERTTEXT | wxSTC_MOD_DELETETEXT);
Bind(wxEVT_COMMAND_TEXT_UPDATED, &SubsEditBox::OnLayerEnter, this, Layer->GetId());
Bind(wxEVT_COMMAND_SPINCTRL_UPDATED, &SubsEditBox::OnLayerChange, this, Layer->GetId());
Bind(wxEVT_COMMAND_TEXT_UPDATED, &SubsEditBox::OnEffectChange, this, Effect->GetId());
Bind(wxEVT_COMMAND_CHECKBOX_CLICKED, &SubsEditBox::OnCommentChange, this, CommentBox->GetId());
Bind(wxEVT_SIZE, &SubsEditBox::OnSize, this);
@ -361,12 +325,12 @@ void SubsEditBox::OnCommit(int type) {
change_value(MarginL, line->GetMarginString(0,false));
change_value(MarginR, line->GetMarginString(1,false));
change_value(MarginV, line->GetMarginString(2,false));
Effect->ChangeValue(line->Effect.empty() ? "Effect" : line->Effect);
Effect->ChangeValue(line->Effect);
CommentBox->SetValue(line->Comment);
StyleBox->Select(StyleBox->FindString(line->Style));
PopulateActorList();
ActorBox->ChangeValue(line->Actor.empty() ? "Actor" : line->Actor);
ActorBox->ChangeValue(line->Actor);
ActorBox->SetStringSelection(line->Actor);
}
}

View file

@ -45,6 +45,7 @@
#include "selection_controller.h"
namespace agi { namespace vfr { class Framerate; } }
namespace agi { struct Context; }
struct AssColor;
class AssDialogue;
@ -61,7 +62,7 @@ class wxStyledTextCtrl;
class wxStyledTextEvent;
class wxTextCtrl;
namespace agi { namespace vfr { class Framerate; } }
template<class Base> class Placeholder;
/// DOCME
/// @class SubsEditBox
@ -92,7 +93,7 @@ class SubsEditBox : public wxPanel, protected SelectionListener<AssDialogue> {
// Box controls
wxCheckBox *CommentBox;
wxComboBox *StyleBox;
wxComboBox *ActorBox;
Placeholder<wxComboBox> *ActorBox;
TimeEdit *StartTime;
TimeEdit *EndTime;
TimeEdit *Duration;
@ -100,7 +101,7 @@ class SubsEditBox : public wxPanel, protected SelectionListener<AssDialogue> {
wxTextCtrl *MarginL;
wxTextCtrl *MarginR;
wxTextCtrl *MarginV;
wxTextCtrl *Effect;
Placeholder<wxTextCtrl> *Effect;
wxRadioButton *ByTime;
wxRadioButton *ByFrame;
@ -159,6 +160,8 @@ class SubsEditBox : public wxPanel, protected SelectionListener<AssDialogue> {
void OnColorButton(AssColor (AssStyle::*field), const char *tag, const char *alt);
void OnFontButton();
void SetPlaceholderCtrl(wxControl *ctrl, wxString const& value);
/// @brief Set the value of a tag for the currently selected text
/// @param tag Tag to set
/// @param value New value of tag