Commit df18ac67 authored by twanvl's avatar twanvl

Fixed TextCtrl to work for keyword properties;

Added wrapping of <> around parameters to TextElement;
Added colors for keyword parameters;
Added menu & toolbar for keyword panel;
Fixed bug in package, save/save-as was the wrong way around;
Added third quality setting to RotatedDC: using SetUserScale, this gets you more precise positioning.
parent 901ee838
......@@ -41,6 +41,12 @@ menu:
rotate 180: Rotated 180°, &Up Side Down
card list columns: C&ard List Columns...
keywords: &Keywords
previous keyword: Select &Previous Keyword PgUp
next keyword: Select &Next Keyword PgDn
add keyword: &Add Keyword Ctrl++
remove keyword: &Remove Select Keyword Del
format: &Format
bold: &Bold Ctrl+B
italic: &Italic Ctrl+I
......@@ -123,6 +129,12 @@ help:
rotate 180: Display the card up side down
card list columns: Select what columns should be shown and in what order.
keywords:
previous keyword: Selects the previous keyword in the list
next keyword: Selects the next keyword in the list
add keyword: Add a new keyword to this set
remove keyword: Delete the selected keyword from this set
format:
bold: Makes the selected text bold
italic: Makes the selected text italic
......@@ -266,6 +278,9 @@ tooltip:
remove card: Remove selected card
rotate card: Rotate card
add keyword: Add keyword
remove keyword: Remove selected keyword
bold: Bold
italic: Italic
symbols: Symbols
......
......@@ -285,6 +285,7 @@ set field:
choice: core
choice: expert
choice: custom
initial: old, core, export, custom
description: Should reminder text be added to keywords by default? Note: you can enable/disable reminder text by right clicking the keyword.
set field:
type: boolean
......@@ -902,6 +903,7 @@ keyword mode:
name: expert
description: Expert level keywords (Cycling, Vanishing, etc.)
keyword mode:
is default: true
name: custom
description: Custom keywords
......
......@@ -31,15 +31,10 @@ IMPLEMENT_REFLECTION(MultipleChoiceField) {
MultipleChoiceStyle::MultipleChoiceStyle(const MultipleChoiceFieldP& field)
: ChoiceStyle(field)
, direction(HORIZONTAL)
, direction(LEFT_TO_RIGHT)
, spacing(0)
{}
IMPLEMENT_REFLECTION_ENUM(Direction) {
VALUE_N("horizontal", HORIZONTAL);
VALUE_N("vertical", VERTICAL);
}
IMPLEMENT_REFLECTION(MultipleChoiceStyle) {
REFLECT_BASE(ChoiceStyle);
REFLECT(direction);
......
......@@ -32,10 +32,6 @@ class MultipleChoiceField : public ChoiceField {
// ----------------------------------------------------------------------------- : MultipleChoiceStyle
enum Direction {
HORIZONTAL, VERTICAL
};
/// The Style for a MultipleChoiceField
class MultipleChoiceStyle : public ChoiceStyle {
public:
......
......@@ -86,6 +86,7 @@ IMPLEMENT_REFLECTION(TextStyle) {
REFLECT(line_height_hard);
REFLECT(line_height_line);
REFLECT_N("mask", mask_filename);
REFLECT(direction);
}
// ----------------------------------------------------------------------------- : TextValue
......@@ -108,15 +109,27 @@ IMPLEMENT_REFLECTION_NAMELESS(TextValue) {
// ----------------------------------------------------------------------------- : FakeTextValue
FakeTextValue::FakeTextValue(const TextFieldP& field, String* underlying, bool untagged)
: TextValue(field), underlying(underlying)
, untagged(untagged)
{
if (underlying) {
value.assign(untagged ? escape(*underlying) : *underlying);
}
}
void FakeTextValue::onAction(Action& a, bool undone) {
*underlying = value;
if (underlying) {
*underlying = untagged ? untag(value) : value;
}
}
bool FakeTextValue::equals(const Value* that) {
if (this == that) return true;
if (!underlying) return false;
const FakeTextValue* thatT = dynamic_cast<const FakeTextValue*>(that);
if (!thatT || underlying != thatT->underlying) return false;
// update the value
value = *underlying;
value.assign(untagged ? escape(*underlying) : *underlying);
return true;
}
......@@ -65,7 +65,8 @@ class TextStyle : public Style {
double line_height_hard; ///< Line height for hard linebreaks
double line_height_line; ///< Line height for <line> tags
String mask_filename; ///< Filename of the mask
ContourMask mask; ///< Mask to fit the text to (may be null)
ContourMask mask; ///< Mask to fit the text to (may be null)
Direction direction; ///< In what direction is text layed out?
virtual bool update(Context&);
virtual void initDependencies(Context&, const Dependency&) const;
......@@ -104,10 +105,12 @@ class TextValue : public Value {
/** Used by TextCtrl */
class FakeTextValue : public TextValue {
public:
inline FakeTextValue(const TextFieldP& field, String* underlying)
: TextValue(field), underlying(underlying) {}
/// Initialize the fake text value
/** underlying can be nullptr, in that case there is no underlying value */
FakeTextValue(const TextFieldP& field, String* underlying, bool untagged);
String* const underlying; ///< The underlying actual value
String* const underlying; ///< The underlying actual value, can be null
bool const untagged; ///< The underlying value is untagged
/// Update underlying data
virtual void onAction(Action& a, bool undone);
......
......@@ -27,7 +27,7 @@ void Font::initDependencies(Context& ctx, const Dependency& dep) const {
shadow_color.initDependencies(ctx, dep);
}
FontP Font::make(bool bold, bool italic, bool placeholder_color) const {
FontP Font::make(bool bold, bool italic, bool placeholder_color, Color* other_color) const {
FontP f(new Font(*this));
if (bold) f->font.SetWeight(wxBOLD);
if (italic) {
......@@ -41,6 +41,9 @@ FontP Font::make(bool bold, bool italic, bool placeholder_color) const {
f->color = f->separator_color;
f->shadow_displacement = RealSize(0,0); // no shadow
}
if (other_color) {
f->color = *other_color;
}
return f;
}
......
......@@ -41,7 +41,7 @@ class Font {
inline bool hasShadow() { return shadow_displacement.width != 0 || shadow_displacement.height != 0; }
/// Make a bold/italic/placeholder version of this font
FontP make(bool bold, bool italic, bool placeholder_color) const;
FontP make(bool bold, bool italic, bool placeholder_color, Color* other_color) const;
private:
DECLARE_REFLECTION();
......
......@@ -34,6 +34,7 @@ IMPLEMENT_REFLECTION(KeywordParam) {
IMPLEMENT_REFLECTION(KeywordMode) {
REFLECT(name);
REFLECT(description);
REFLECT(is_default);
}
// backwards compatability
......@@ -376,7 +377,7 @@ String KeywordDatabase::expand(const String& text,
String param = untagged.substr(start_u, len_u); // untagged version
if (param.empty()) {
// placeholder
param = _("<atom-kwpph>") + (kwp.placeholder.empty() ? kwp.name : kwp.placeholder) + _("</atom-kwpph>");
param = _("<atom-kwpph>") + (kwp.placeholder.empty() ? kwp.name : kwp.placeholder) + _("</atom-kwpph>");
part = part + param; // keep tags
} else if (kw->parameters[j/2-1]->script) {
// apply parameter script
......
......@@ -41,6 +41,7 @@ class KeywordParam {
class KeywordMode {
String name; ///< Name of the mode
String description; ///< Description of the type
bool is_default; ///< This is the default mode for new keywords
DECLARE_REFLECTION();
};
......
......@@ -13,6 +13,7 @@
#include <gui/control/card_viewer.hpp>
class ValueEditor;
class FindInfo;
// ----------------------------------------------------------------------------- : DataEditor
......@@ -58,6 +59,16 @@ class DataEditor : public CardViewer {
/// A menu item from getMenu was selected
void onCommand(int id);
// --------------------------------------------------- : Search/replace
/// Do a search or replace action for the given FindInfo
/** If from_start == false: searches only from the current selection onward (or backward)
* If from_start == true: searches everything
*
* Returns true when more searching is needed.
*/
bool search(FindInfo& find, bool from_start);
// --------------------------------------------------- : ValueViewers
protected:
......
......@@ -246,7 +246,7 @@ void GraphControl::setData(const GraphDataP& data) {
void GraphControl::onPaint(wxPaintEvent&) {
wxBufferedPaintDC dc(this);
wxSize cs = GetClientSize();
RotatedDC rdc(dc, 0, RealRect(RealPoint(0,0),cs), 1, false);
RotatedDC rdc(dc, 0, RealRect(RealPoint(0,0),cs), 1, QUALITY_LOW);
rdc.SetPen(*wxTRANSPARENT_PEN);
rdc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
rdc.DrawRectangle(rdc.getInternalRect());
......
......@@ -26,7 +26,7 @@ Rotation NativeLookEditor::getRotation() const {
}
void NativeLookEditor::draw(DC& dc) {
RotatedDC rdc(dc, getRotation(), false);
RotatedDC rdc(dc, getRotation(), QUALITY_LOW);
DataViewer::draw(rdc, wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
}
void NativeLookEditor::drawViewer(RotatedDC& dc, ValueViewer& v) {
......
......@@ -16,9 +16,10 @@ DECLARE_TYPEOF_COLLECTION(ValueViewerP);
// ----------------------------------------------------------------------------- : TextCtrl
TextCtrl::TextCtrl(Window* parent, int id, long style)
TextCtrl::TextCtrl(Window* parent, int id, bool multi_line, long style)
: DataEditor(parent, id, style)
, value(nullptr)
, multi_line(multi_line)
{}
Rotation TextCtrl::getRotation() const {
......@@ -26,44 +27,37 @@ Rotation TextCtrl::getRotation() const {
}
void TextCtrl::draw(DC& dc) {
RotatedDC rdc(dc, getRotation(), false);
RotatedDC rdc(dc, getRotation(), QUALITY_LOW);
DataViewer::draw(rdc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
}
void TextCtrl::setValue(String* value) {
this->value = value;
if (viewers.empty() && value) {
// create a field, style and value
TextFieldP field(new TextField);
TextStyleP style(new TextStyle(field));
TextValueP value(new FakeTextValue(field, this->value));
// set stuff
field->index = 0;
field->multi_line = true;
style->width = 100;
style->height = 20;
style->left = style->top = 1;
value->value.assign(*this->value);
// assign to this control
IndexMap<FieldP,StyleP> styles; styles.add(field, style);
IndexMap<FieldP,ValueP> values; values.add(field, value);
setStyles(set->stylesheet, styles);
setData(values);
// determine size
wxSize cs = GetClientSize();
style->width = cs.GetWidth() - 2;
style->height = cs.GetHeight() - 2;
viewers.front()->getEditor()->determineSize(true);
// We don't wan to change the window size
//SetMinSize(RealSize(style->width + 6, style->height + 6));
} else if (value) {
TextStyle& TextCtrl::getStyle() {
assert(!viewers.empty());
return static_cast<TextStyle&>(*viewers.front()->getStyle());
}
TextField& TextCtrl::getField() {
assert(!viewers.empty());
return static_cast<TextField&>(*viewers.front()->getField());
}
void TextCtrl::updateSize() {
wxSize cs = GetClientSize();
Style& style = getStyle();
style.width = cs.GetWidth() - 2;
style.height = cs.GetHeight() - 2;
viewers.front()->getEditor()->determineSize(true);
}
void TextCtrl::setValue(String* value, bool untagged) {
if (value != this->value) {
this->value = value;
// create a new value, for a different underlying actual value
ValueViewer& viewer = *viewers.front();
TextValueP new_value(new FakeTextValue(static_pointer_cast<TextField>(viewer.getField()), this->value));
ValueViewer& viewer = *viewers.front();
TextValueP new_value(new FakeTextValue(static_pointer_cast<TextField>(viewer.getField()), this->value, untagged));
viewer.setValue(new_value);
updateSize();
valueChanged();
}
valueChanged();
}
void TextCtrl::valueChanged() {
if (!viewers.empty()) {
......@@ -87,7 +81,29 @@ void TextCtrl::onAction(const Action& action, bool undone) {
}
void TextCtrl::onChangeSet() {
DataEditor::onChangeSet();
setValue(nullptr);
// initialize
if (viewers.empty()) {
// create a field, style and value
TextFieldP field(new TextField);
TextStyleP style(new TextStyle(field));
TextValueP value(new FakeTextValue(field, nullptr, false));
// set stuff
field->index = 0;
field->multi_line = multi_line;
style->width = 100;
style->height = 20;
style->left = style->top = 1;
style->font.color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
// assign to this control
IndexMap<FieldP,StyleP> styles; styles.add(field, style);
IndexMap<FieldP,ValueP> values; values.add(field, value);
setStyles(set->stylesheet, styles);
setData(values);
updateSize();
onChange();
} else {
setValue(nullptr);
}
}
void TextCtrl::onInit() {
......@@ -99,16 +115,19 @@ void TextCtrl::onInit() {
void TextCtrl::onSize(wxSizeEvent&) {
if (!viewers.empty()) {
wxSize cs = GetClientSize();
Style& style = *viewers.front()->getStyle();
style.width = cs.GetWidth() - 2;
style.height = cs.GetHeight() - 2;
viewers.front()->getEditor()->determineSize(true);
updateSize();
onChange();
}
onChange();
}
wxSize TextCtrl::DoGetBestSize() const {
return wxSize(1,1);
if (multi_line || viewers.empty()) {
// flexible size
return wxSize(1,1);
} else {
wxSize ws = GetSize(), cs = GetClientSize();
Style& style = *viewers.front()->getStyle();
return wxSize(style.width, style.height) + ws - cs;
}
}
BEGIN_EVENT_TABLE(TextCtrl, DataEditor)
......
......@@ -12,6 +12,9 @@
#include <util/prec.hpp>
#include <gui/control/card_editor.hpp>
class TextField;
class TextStyle;
// ----------------------------------------------------------------------------- : TextCtrl
/// A control for editing a String
......@@ -25,13 +28,20 @@
*/
class TextCtrl : public DataEditor {
public:
TextCtrl(Window* parent, int id, long style = 0);
TextCtrl(Window* parent, int id, bool multi_line, long style = 0);
/// Set the value that is being edited
void setValue(String* value);
void setValue(String* value, bool untagged = false);
/// Notification that the value has changed outside this control
void valueChanged();
/// Get access to the field used by the control
TextField& getField();
/// Get access to the style used by the control
TextStyle& getStyle();
/// Update the size, for example after changing the style
void updateSize();
/// Uses a native look
virtual bool nativeLook() const { return true; }
virtual bool drawBorders() const { return false; }
......@@ -48,7 +58,8 @@ class TextCtrl : public DataEditor {
virtual wxSize DoGetBestSize() const;
private:
String* value; ///< Value to edit
String* value; ///< Value to edit
bool multi_line; ///< Multi line text control?
DECLARE_EVENT_TABLE();
......
......@@ -367,7 +367,7 @@ void ImageSlicePreview::draw(DC& dc) {
wxMemoryDC mdc; mdc.SelectObject(bitmap);
// draw checker pattern behind image
RealRect rect = GetClientSize();
RotatedDC rdc(mdc, 0, rect, 1, false);
RotatedDC rdc(mdc, 0, rect, 1, QUALITY_LOW);
draw_checker(rdc, rect);
rdc.DrawImage(image, RealPoint(0,0));
mdc.SelectObject(wxNullBitmap);
......
......@@ -31,7 +31,7 @@ CardsPanel::CardsPanel(Window* parent, int id)
splitter = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
card_list = new ImageCardList(splitter, ID_CARD_LIST);
notesP = new Panel(splitter, wxID_ANY);
notes = new TextCtrl(notesP, ID_NOTES);
notes = new TextCtrl(notesP, ID_NOTES, true);
collapse_notes = new HoverButton(notesP, ID_COLLAPSE_NOTES, _("btn_collapse"), wxNullColour);
collapse_notes->SetExtraStyle(wxWS_EX_PROCESS_UI_UPDATES);
// init sizer for notes panel
......
......@@ -27,7 +27,7 @@ class CardsPanel : public SetWindowPanel {
CardsPanel(Window* parent, int id);
~CardsPanel();
void onChangeSet();
virtual void onChangeSet();
// --------------------------------------------------- : UI
......
......@@ -9,7 +9,11 @@
#include <gui/set/keywords_panel.hpp>
#include <gui/control/keyword_list.hpp>
#include <gui/control/text_ctrl.hpp>
#include <gui/icon_menu.hpp>
#include <gui/util.hpp>
#include <data/keyword.hpp>
#include <data/field/text.hpp>
#include <util/window_id.hpp>
#include <wx/listctrl.h>
#include <wx/splitter.h>
......@@ -19,16 +23,17 @@ KeywordsPanel::KeywordsPanel(Window* parent, int id)
: SetWindowPanel(parent, id)
{
// init controls
Panel* panel;
splitter = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
list = new KeywordList(splitter, wxID_ANY);
panel = new Panel(splitter, wxID_ANY);
keyword = new TextCtrl(panel, wxID_ANY);
match = new TextCtrl(panel, wxID_ANY);
reminder = new TextCtrl(panel, wxID_ANY);
rules = new TextCtrl(panel, wxID_ANY);
keyword = new TextCtrl(panel, wxID_ANY, false);
match = new TextCtrl(panel, wxID_ANY, false);
reminder = new TextCtrl(panel, wxID_ANY, false);
rules = new TextCtrl(panel, wxID_ANY, true);
// init sizer for panel
wxSizer* sp = new wxBoxSizer(wxVERTICAL);
sp->Add(new wxStaticText(panel, wxID_ANY, _("This is a standard $game keyword, you can not edit it. ")
_("If you make a copy of the keyword your copy will take precedent.")), 0, wxALL, 6);
sp->Add(new wxStaticText(panel, wxID_ANY, _("Keyword:")), 0, wxALL, 6);
sp->Add(keyword, 0, wxEXPAND | wxALL & ~wxTOP, 6);
wxSizer* s2 = new wxStaticBoxSizer(wxVERTICAL, panel, _("Match"));
......@@ -38,13 +43,14 @@ KeywordsPanel::KeywordsPanel(Window* parent, int id)
sp->Add(s2, 0, wxEXPAND | wxALL, 6);
sp->Add(new wxStaticText(panel, wxID_ANY, _("Reminder:")), 0, wxALL, 6);
sp->Add(reminder, 0, wxEXPAND | wxALL & ~wxTOP, 6);
sp->Add(new wxStaticText(panel, wxID_ANY, _("Example:")), 0, wxALL, 6);
sp->Add(new wxStaticText(panel, wxID_ANY, _("Rules:")), 0, wxALL, 6);
sp->Add(rules, 0, wxEXPAND | wxALL & ~wxTOP, 6);
sp->Add(rules, 1, wxEXPAND | wxALL & ~wxTOP, 6);
panel->SetSizer(sp);
// init splitter
splitter->SetMinimumPaneSize(100);
splitter->SetSashGravity(0.5);
splitter->SplitVertically(list, panel, -200);
splitter->SplitVertically(list, panel);
// init sizer
wxSizer* s = new wxBoxSizer(wxHORIZONTAL);
s->Add(splitter, 1, wxEXPAND);
......@@ -55,8 +61,102 @@ KeywordsPanel::KeywordsPanel(Window* parent, int id)
/* wxSizer* s2 = new wxBoxSizer(wxVERTICAL);
s2->Add(list_active, 1, wxEXPAND);
s2->Add(list_inactive, 1, wxEXPAND);*/
// init menus
menuKeyword = new IconMenu();
menuKeyword->Append(ID_KEYWORD_PREV, _MENU_("previous keyword"), _HELP_("previous keyword"));
menuKeyword->Append(ID_KEYWORD_NEXT, _MENU_("next keyword"), _HELP_("next keyword"));
menuKeyword->AppendSeparator();
menuKeyword->Append(ID_KEYWORD_ADD, _("keyword_add"), _MENU_("add keyword"), _HELP_("add keyword"));
// NOTE: space after "Del" prevents wx from making del an accellerator
// otherwise we delete a card when delete is pressed inside the editor
menuKeyword->Append(ID_KEYWORD_REMOVE, _("keyword_del"), _MENU_("remove keyword")+_(" "),_HELP_("remove keyword"));
}
KeywordsPanel::~KeywordsPanel() {
delete menuKeyword;
}
// ----------------------------------------------------------------------------- : UI
void KeywordsPanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
// Toolbar
tb->AddTool(ID_KEYWORD_ADD, _(""), load_resource_tool_image(_("keyword_add")), wxNullBitmap, wxITEM_NORMAL,_TOOLTIP_("add keyword"), _HELP_("add keyword"));
tb->AddTool(ID_KEYWORD_REMOVE, _(""), load_resource_tool_image(_("keyword_del")), wxNullBitmap, wxITEM_NORMAL,_TOOLTIP_("remove keyword"),_HELP_("remove keyword"));
tb->Realize();
// Menus
mb->Insert(2, menuKeyword, _MENU_("keywords"));
}
void KeywordsPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
// Toolbar
tb->DeleteTool(ID_KEYWORD_ADD);
tb->DeleteTool(ID_KEYWORD_REMOVE);
// Menus
mb->Remove(2);
}
void KeywordsPanel::onUpdateUI(wxUpdateUIEvent& ev) {
switch (ev.GetId()) {
case ID_KEYWORD_PREV: ev.Enable(list->canSelectPrevious()); break;
case ID_KEYWORD_NEXT: ev.Enable(list->canSelectNext()); break;
case ID_KEYWORD_REMOVE: ev.Enable(list->getKeyword() && !list->getKeyword()->fixed); break;
}
}
void KeywordsPanel::onCommand(int id) {
switch (id) {
case ID_KEYWORD_PREV:
list->selectPrevious();
break;
case ID_KEYWORD_NEXT:
list->selectNext();
break;
case ID_KEYWORD_ADD:
// set->actions.add(new AddKeywordAction(*set));
break;
case ID_KEYWORD_REMOVE:
// set->actions.add(new RemoveKeywordAction(*set, list->getKeyword()));
break;
}
}
// ----------------------------------------------------------------------------- : Events
void KeywordsPanel::onChangeSet() {
list->setSet(set);
// init text controls
keyword ->setSet(set);
keyword ->getStyle().font.size = 16;
keyword ->getStyle().font.font.SetPointSize(16);
keyword ->getStyle().padding_bottom = 1;
keyword ->updateSize();
match ->setSet(set);
match ->getStyle().font.size = 12;
match ->getStyle().font.font.SetPointSize(12);
match ->getStyle().padding_bottom = 1;
match ->updateSize();
reminder->setSet(set);
reminder->getStyle().padding_bottom = 2;
reminder->updateSize();
rules ->setSet(set);
// re-layout
panel->Layout();
}
void KeywordsPanel::onKeywordSelect(KeywordSelectEvent& ev) {
if (ev.keyword) {
panel->Enable(!ev.keyword->fixed);
keyword->setValue(&ev.keyword->keyword, true);
match ->setValue(&ev.keyword->match);
rules ->setValue(&ev.keyword->rules);
} else {
panel->Enable(false);
}
}
BEGIN_EVENT_TABLE(KeywordsPanel, wxPanel)
EVT_KEYWORD_SELECT(wxID_ANY, KeywordsPanel::onKeywordSelect)
END_EVENT_TABLE()
......@@ -15,6 +15,8 @@
class wxSplitterWindow;
class KeywordList;
class TextCtrl;
class IconMenu;
struct KeywordSelectEvent;
// ----------------------------------------------------------------------------- : KeywordsPanel
......@@ -22,17 +24,32 @@ class TextCtrl;
class KeywordsPanel : public SetWindowPanel {
public:
KeywordsPanel(Window* parent, int id);
~KeywordsPanel();
virtual void onChangeSet();
// --------------------------------------------------- : UI
virtual void initUI (wxToolBar* tb, wxMenuBar* mb);
virtual void destroyUI(wxToolBar* tb, wxMenuBar* mb);
virtual void onUpdateUI(wxUpdateUIEvent&);
virtual void onCommand(int id);
private:
DECLARE_EVENT_TABLE();
// --------------------------------------------------- : Controls
wxSplitterWindow* splitter;
wxPanel* panel;
KeywordList* list;
TextCtrl* keyword;
TextCtrl* match;
TextCtrl* reminder;
TextCtrl* rules;
IconMenu* menuKeyword;
// --------------------------------------------------- : Events
void onKeywordSelect(KeywordSelectEvent& ev);
};
// ----------------------------------------------------------------------------- : EOF
......
......@@ -94,6 +94,17 @@ class ValueEditor {
virtual size_t selectionStart() const { return 0; }
virtual size_t selectionEnd() const { return 0; }
// --------------------------------------------------- : Search / replace
/// Do a search or replace action for the given FindInfo
/** If from_start == false: searches only from the current selection onward (or backward),
* excluding the sellection itself.
* If from_start == true: searches everything
*
* Returns true when more searching is needed.
*/
bool search(FindInfo& find, bool from_start);
// --------------------------------------------------- : Other
/// The cursor type to use when the mouse is over this control
......
......@@ -529,7 +529,7 @@ void TextValueEditor::moveSelection(IndexType t, size_t new_end, bool also_move_
{
// Move selection
shared_ptr<DC> dc = editor().overdrawDC();
RotatedDC rdc(*dc, viewer.getRotation(), false);
RotatedDC rdc(*dc, viewer.getRotation(), QUALITY_LOW);
if (nativeLook()) {
// clip the dc to the region of this control
rdc.SetClippingRegion(style().getRect());
......@@ -659,7 +659,7 @@ void TextValueEditor::determineSize(bool force_fit) {
Bitmap bmp(1,1);
dc.SelectObject(bmp);
dc.SetFont(style().font.font);
style().height = dc.GetCharHeight() + 2;
style().height = dc.GetCharHeight() + 2 + style().padding_top + style().padding_bottom;
}
}
......
......@@ -15,6 +15,24 @@
#include <render/value/text.hpp>
class TextValueEditorScrollBar;
class wxFindReplaceData;
DECLARE_POINTER_TYPE(Card);
// ----------------------------------------------------------------------------- : Search/replace
/// Information for search/replace
class FindInfo {
public:
FindInfo(wxFindReplaceData& what) : what(what) {}
virtual ~FindInfo() {}
/// Handle that a match was found.
/** Should return whether more searching is needed.
*/
virtual bool handle(const CardP& card, const TextValueP& value, size_t start, size_t end) = 0;
wxFindReplaceData& what; ///< What to search for, the direction to search in
};
// ----------------------------------------------------------------------------- : TextValueEditor
......
......@@ -27,7 +27,9 @@ DECLARE_TYPEOF_NO_REV(IndexMap<FieldP COMMA StyleP>);
void DataViewer::draw(DC& dc) {
StyleSheetSettings& ss = settings.stylesheetSettingsFor(*stylesheet);
RotatedDC rdc(dc, ss.card_angle(), stylesheet->getCardRect(), ss.card_zoom(), ss.card_anti_alias() && !nativeLook(), true);
RotatedDC rdc(dc, ss.card_angle(), stylesheet->getCardRect(), ss.card_zoom(),
nativeLook() ? QUALITY_LOW : (ss.card_anti_alias() ? QUALITY_AA : QUALITY_SUB_PIXEL),
true);
draw(rdc, stylesheet->card_background);
}
void DataViewer::draw(RotatedDC& dc, const Color& background) {
......
......@@ -11,6 +11,7 @@
#include <data/field/text.hpp>
DECLARE_TYPEOF_COLLECTION(TextElementP);
DECLARE_POINTER_TYPE(FontTextElement);
// ----------------------------------------------------------------------------- : TextElements
......@@ -56,14 +57,26 @@ double TextElements::scaleStep() const {
return m;
}
// Colors for <param> tags
Color param_colors[] =
{ Color(0,170,0)
, Color(0,0,200)
, Color(200,0,100)
, Color(200,200,0)
, Color(0,170,170)
, Color(200,0,0)
};
// Helper class for TextElements::fromString, to allow persistent formating state accross recusive calls
struct TextElementsFromString {
// What formatting is enabled?
int bold, italic, symbol;
int soft, kwpph, line;
int soft, kwpph, param, line;
int param_id;
bool bracket;
TextElementsFromString()
: bold(0), italic(0), symbol(0), soft(0), kwpph(0), line(0) {}
: bold(0), italic(0), symbol(0), soft(0), kwpph(0), param(0), line(0), param_id(0), bracket(false) {}
// read TextElements from a string
void fromString(TextElements& te, const String& text, size_t start, size_t end, const TextStyle& style, Context& ctx) {
......@@ -84,6 +97,8 @@ struct TextElementsFromString {
else if (is_substr(text, tag_start, _("</sep-soft"))) soft -= 1;
else if (is_substr(text, tag_start, _( "<atom-kwpph"))) kwpph += 1;
else if (is_substr(text, tag_start, _("</atom-kwpph"))) kwpph -= 1;
else if (is_substr(text, tag_start, _( "<param"))) param += 1;
else if (is_substr(text, tag_start, _("</param"))) param -= 1;
else if (is_substr(text, tag_start, _( "<line"))) line += 1;
else if (is_substr(text, tag_start, _("</line"))) line -= 1;
else if (is_substr(text, tag_start, _("<atom"))) {
......@@ -97,21 +112,40 @@ struct TextElementsFromString {
// ignore other tags
}
} else {
if (c == _('\1')) c = _('<'); // unescape
// A character of normal text, add to the last text element (if possible)
SimpleTextElement* e = nullptr;
if (!te.elements.empty()) e = dynamic_cast<SimpleTextElement*>(te.elements.back().get());
if (e && e->end == pos) {
e->end = pos + 1; // just move the end, no need to make a new element
if (e && e->end == (bracket ? pos + 1 : pos)) {
e->end = bracket ? pos + 2 : pos + 1; // just move the end, no need to make a new element
e->content += c;
if (bracket) {
// content is "<somethin>g" should be "<something>"
swap(e->content[e->content.size() - 2], e->content[e->content.size() - 1]);
}
} else {
// add a new element for this text
if (symbol > 0 && style.symbol_font.valid()) {
te.elements.push_back(new_shared5<SymbolTextElement>(text, pos, pos + 1, style.symbol_font, &ctx));
e = new SymbolTextElement(text, pos, pos + 1, style.symbol_font, &ctx);
bracket = false;
} else {
FontP font = style.font.make(bold > 0, italic > 0, soft > 0 || kwpph > 0,
param > 0 ? &param_colors[(param_id++) % (sizeof(param_colors)/sizeof(param_colors[0]))] : nullptr);
bracket = kwpph > 0 || param > 0;
e = new FontTextElement(
text,
bracket ? pos - 1 : pos,
bracket ? pos + 2 : pos + 1,
font,
soft > 0 ? DRAW_ACTIVE : DRAW_NORMAL,
line > 0 ? BREAK_LINE : BREAK_HARD);
}
if (bracket) {
e->content = String(_("")) + c + _("");
} else {
te.elements.push_back(new_shared6<FontTextElement> (text, pos, pos + 1,
style.font.make(bold > 0, italic > 0, soft > 0 || kwpph > 0),
soft > 0 ? DRAW_ACTIVE : DRAW_NORMAL,
line > 0 ? BREAK_LINE : BREAK_HARD));
e->content = c;
}
te.elements.push_back(TextElementP(e));
}
pos += 1;
}
......
......@@ -100,7 +100,10 @@ class TextElements : public vector<TextElementP> {
/// A text element that just shows text
class SimpleTextElement : public TextElement {
public:
SimpleTextElement(const String& text, size_t start ,size_t end) : TextElement(text, start, end) {}
SimpleTextElement(const String& text, size_t start, size_t end)
: TextElement(text, start, end), content(text.substr(start,end-start))
{}
String content; ///< Text to show
};
/// A text element that uses a normal font
......
......@@ -14,15 +14,18 @@
void FontTextElement::draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const {
if ((what & draw_as) != draw_as) return; // don't draw
dc.SetFont(font->font, font->size * scale);
if (end != start && text.substr(end-1, 1) == _("\n")) end -= 1; // don't draw the newline character at the end
// draw shadow
String text = content.substr(start - this->start, end - start);
if (!text.empty() && text.GetChar(text.size() - 1) == _('\n')) {
text = text.substr(0, text.size() - 1); // don't draw last \n
}
if (font->hasShadow()) {
dc.SetTextForeground(font->shadow_color);
dc.DrawText(text.substr(start, end - start), rect.position() + font->shadow_displacement);
dc.DrawText(text, rect.position() + font->shadow_displacement);
}
// draw
dc.SetTextForeground(font->color);
dc.DrawText(text.substr(start, end - start), rect.position());
dc.DrawText(text, rect.position());
}
void FontTextElement::getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const {
......@@ -31,8 +34,8 @@ void FontTextElement::getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>&
// find sizes & breaks
double prev_width = 0;
for (size_t i = start ; i < end ; ++i) {
Char c = text.GetChar(i);
RealSize s = dc.GetTextExtent(text.substr(start, i - start + 1));
Char c = content.GetChar(i - this->start);
RealSize s = dc.GetTextExtent(content.substr(start - this->start, i - start + 1));
out.push_back(CharInfo(RealSize(s.width - prev_width, s.height),
c == _('\n') ? break_style :
c == _(' ') ? BREAK_SOFT : BREAK_NO
......
......@@ -13,13 +13,13 @@
void SymbolTextElement::draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const {
if (font.font) {
font.font->draw(dc, ctx, rect, font.size * scale, font.alignment, text.substr(start, end-start));
font.font->draw(dc, ctx, rect, font.size * scale, font.alignment, content.substr(start - this->start, end-start));
}
}
void SymbolTextElement::getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const {
if (font.font) {
font.font->getCharInfo(dc, ctx, font.size * scale, text.substr(start, end-start), out);
font.font->getCharInfo(dc, ctx, font.size * scale, content.substr(start - this->start, end-start), out);
}
}
......
......@@ -325,6 +325,36 @@ void TextViewer::prepareLines(RotatedDC& dc, const String& text, const TextStyle
scale = next_scale;
}
/*
double scale_1 = 1.
double fit_1 = fitLines(dc, text, style, scale_1);
if (fit_1 <= 0 || scale_1 >= scale_2) {
// ok
} else {
// find best text size, using the 'false position' root finding method
double scale_2 = elements.minScale();
double fit_2 = fitLines(dc, text, style, scale_2);
if (fit_2 > 0) {
// still doesn't fit at smallest size
} else {
// invariant: fit_1 > 0 && fit_2 <= 0
while (abs(scale_2 - scale_1) > 0.01) {
double scale_3 = scale_2 - fit_2 * (scale_2 - scale_1)/(fit_2 - fit_1);
double fit_3 = fitLines(dc, text, style, scale_3);
if (fit_3 > 0) {
scale_2 = scale_3;
fit_2 = fit_3;
} else {
scale_2 = scale_3;
fit_2 = fit_3;
}
}
}
}
// returns negative values if it fits, positive if it doesn't
*/
// no text, find a dummy height for the single line we have
if (lines.size() == 1 && lines[0].width() < 0.0001) {
if (style.always_symbol && style.symbol_font.valid()) {
......
......@@ -88,7 +88,7 @@ Bitmap ImageValueViewer::imagePlaceholder(const Rotation& rot, UInt w, UInt h, b
wxMemoryDC mdc;
mdc.SelectObject(bmp);
RealRect rect(0,0,w,h);
RotatedDC dc(mdc, 0, rect, 1.0, true);
RotatedDC dc(mdc, 0, rect, 1.0, QUALITY_AA);
// Draw checker background
draw_checker(dc, rect);
// Draw text
......
......@@ -18,7 +18,7 @@ DECLARE_TYPEOF_COLLECTION(String);
void MultipleChoiceValueViewer::draw(RotatedDC& dc) {
drawFieldBorder(dc);
if (style().render_style & RENDER_HIDDEN) return;
RealPoint pos = style().getPos();
RealPoint pos = align_in_rect(style().alignment, RealSize(0,0), style().getRect());
// selected choices
vector<String> selected;
value().get(selected);
......@@ -66,9 +66,6 @@ void MultipleChoiceValueViewer::drawChoice(RotatedDC& dc, RealPoint& pos, const
RealRect(pos + RealSize(size.width + 1, 0), RealSize(0,size.height))));
size = add_horizontal(size, text_size);
}
if (style().direction == HORIZONTAL) {
pos.x += size.width + style().spacing;
} else {
pos.y += size.height + style().spacing;
}
// next position
pos = move_in_direction(style().direction, pos, size, style().spacing);
}
......@@ -79,3 +79,24 @@ template <> void Writer::handle(const Alignment& align) {
template <> void GetDefaultMember::handle(const Alignment& align) {
handle(to_string(align));
}
// ----------------------------------------------------------------------------- : Direction
IMPLEMENT_REFLECTION_ENUM(Direction) {
VALUE_N("left to right", LEFT_TO_RIGHT);
VALUE_N("right to left", RIGHT_TO_LEFT);
VALUE_N("top to bottom", TOP_TO_BOTTOM);
VALUE_N("bottom to top", BOTTOM_TO_TOP);
VALUE_N("horizontal", LEFT_TO_RIGHT);
VALUE_N("vertical", TOP_TO_BOTTOM);
}
RealPoint move_in_direction(Direction dir, const RealPoint& point, const RealSize to_move, double spacing) {
switch (dir) {
case LEFT_TO_RIGHT: return RealPoint(point.x + to_move.width + spacing, point.y);
case RIGHT_TO_LEFT: return RealPoint(point.x - to_move.width - spacing, point.y);
case TOP_TO_BOTTOM: return RealPoint(point.x, point.y + to_move.height + spacing);
case BOTTOM_TO_TOP: return RealPoint(point.x, point.y - to_move.height - spacing);
default: return point; // should not happen
}
}
......@@ -52,5 +52,17 @@ double align_delta_y(Alignment align, double box_height, double obj_height);
*/
RealPoint align_in_rect(Alignment align, const RealSize& to_align, const RealRect& outer);
// ----------------------------------------------------------------------------- : Direction
/// Direction to place something in
enum Direction {
LEFT_TO_RIGHT, RIGHT_TO_LEFT,
TOP_TO_BOTTOM, BOTTOM_TO_TOP
};
/// Move a point in a direction
/** If the direction is horizontal the to_move.width is used, otherwise to_move.height */
RealPoint move_in_direction(Direction dir, const RealPoint& point, const RealSize to_move, double spacing = 0);
// ----------------------------------------------------------------------------- : EOF
#endif
......@@ -41,7 +41,7 @@ bool Package::isOpened() const {
return !filename.empty();
}
bool Package::needSaveAs() const {
return !filename.empty();
return filename.empty();
}
String Package::name() const {
// wxFileName is too slow (profiled)
......
......@@ -111,24 +111,31 @@ Rotater::~Rotater() {
// ----------------------------------------------------------------------------- : RotatedDC
RotatedDC::RotatedDC(DC& dc, int angle, const RealRect& rect, double zoom, bool high_quality, bool is_internal)
RotatedDC::RotatedDC(DC& dc, int angle, const RealRect& rect, double zoom, RenderQuality quality, bool is_internal)
: Rotation(angle, rect, zoom, is_internal)
, dc(dc), high_quality(high_quality)
, dc(dc), quality(quality)
{}
RotatedDC::RotatedDC(DC& dc, const Rotation& rotation, bool high_quality)
RotatedDC::RotatedDC(DC& dc, const Rotation& rotation, RenderQuality quality)
: Rotation(rotation)
, dc(dc), high_quality(high_quality&&false)
, dc(dc), quality(quality)
{}
// ----------------------------------------------------------------------------- : RotatedDC : Drawing
void RotatedDC::DrawText (const String& text, const RealPoint& pos) {
if (text.empty()) return;
if (high_quality) {
if (quality == QUALITY_AA) {
RealRect r(pos, GetTextExtent(text));
RealRect r_ext = trNoNeg(r);
draw_resampled_text(dc, r_ext, revX(), revY(), angle, text);
} else if (quality == QUALITY_SUB_PIXEL) {
RealPoint p_ext = tr(pos)*text_scaling;
double usx,usy;
dc.GetUserScale(&usx, &usy);
dc.SetUserScale(usx/text_scaling, usy/text_scaling);
dc.DrawRotatedText(text, (int) p_ext.x, (int) p_ext.y, angle);
dc.SetUserScale(usx, usy);
} else {
RealPoint p_ext = tr(pos);
dc.DrawRotatedText(text, (int) p_ext.x, (int) p_ext.y, angle);
......@@ -172,24 +179,44 @@ void RotatedDC::SetTextForeground(const Color& color) { dc.SetTextForeground(col
void RotatedDC::SetLogicalFunction(int function) { dc.SetLogicalFunction(function); }
void RotatedDC::SetFont(const wxFont& font) {
SetFont(font, font.GetPointSize());
if (quality == QUALITY_LOW) {
dc.SetFont(font);
} else {
SetFont(font, font.GetPointSize());
}
}
void RotatedDC::SetFont(wxFont font, double size) {
font.SetPointSize((int) (trS(size) * (high_quality ? text_scaling : 1)));
if (quality == QUALITY_LOW) {
font.SetPointSize((int) trS(size));
} else {
font.SetPointSize((int) (trS(size) * text_scaling));
}
dc.SetFont(font);
}
double RotatedDC::getFontSizeStep() const {
return 1. / (high_quality ? text_scaling : 1);
if (quality == QUALITY_LOW) {
return 1;
} else {
return 1. / text_scaling;
}
}
RealSize RotatedDC::GetTextExtent(const String& text) const {
int w, h;
dc.GetTextExtent(text, &w, &h);
return RealSize(w,h) / zoom / (high_quality ? text_scaling : 1);
if (quality == QUALITY_LOW) {
return RealSize(w,h) / zoom;
} else {
return RealSize(w,h) / zoom / text_scaling;
}
}
double RotatedDC::GetCharHeight() const {
return dc.GetCharHeight() / zoom / (high_quality ? text_scaling : 1);
if (quality == QUALITY_LOW) {
return dc.GetCharHeight() / zoom;
} else {
return dc.GetCharHeight() / zoom / text_scaling;
}
}
void RotatedDC::SetClippingRegion(const RealRect& rect) {
......
......@@ -119,13 +119,20 @@ class Rotater {
// ----------------------------------------------------------------------------- : RotatedDC
/// Render quality of text
enum RenderQuality {
QUALITY_AA, ///< Our own anti aliassing
QUALITY_SUB_PIXEL, ///< Sub-pixel positioning
QUALITY_LOW, ///< Normal
};
/// A DC with rotation applied
/** All draw** functions take internal coordinates.
*/
class RotatedDC : public Rotation {
public:
RotatedDC(DC& dc, int angle, const RealRect& rect, double zoom, bool high_quality, bool is_internal = false);
RotatedDC(DC& dc, const Rotation& rotation, bool high_quality);
RotatedDC(DC& dc, int angle, const RealRect& rect, double zoom, RenderQuality quality, bool is_internal = false);
RotatedDC(DC& dc, const Rotation& rotation, RenderQuality quality);
// --------------------------------------------------- : Drawing
......@@ -165,8 +172,8 @@ class RotatedDC : public Rotation {
inline wxDC& getDC() { return dc; }
private:
wxDC& dc; ///< The actual dc
bool high_quality; ///< Drawing using our own anti aliassing?
wxDC& dc; ///< The actual dc
RenderQuality quality; ///< Quality of the text
};
// ----------------------------------------------------------------------------- : EOF
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment