Skip to content

Commit

Permalink
Merge branch 'workarounds' into feature
Browse files Browse the repository at this point in the history
  • Loading branch information
arch1t3cht committed Oct 27, 2023
2 parents dd303b1 + 6dda04d commit fac664a
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ if host_machine.system() == 'darwin'
'font_file_lister_coretext.mm',
'osx/osx_utils.mm',
'osx/retina_helper.mm',
'osx/scintilla_ime.mm',
)
elif host_machine.system() == 'windows'
aegisub_src += files(
Expand Down
212 changes: 212 additions & 0 deletions src/osx/scintilla_ime.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// Copyright (c) 2014, Thomas Goyne <[email protected]>
//
// 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/

#import <objc/runtime.h>
#import <wx/osx/private.h>
#import <wx/stc/stc.h>

// from src/osx/cocoa/window.mm
@interface wxNSView : NSView <NSTextInputClient> {
BOOL _hasToolTip;
NSTrackingRectTag _lastToolTipTrackTag;
id _lastToolTipOwner;
void *_lastUserData;
}
@end

@interface IMEState : NSObject
@property (nonatomic) NSRange markedRange;
@property (nonatomic) bool undoActive;
@end

@implementation IMEState
- (id)init {
self = [super init];
self.markedRange = NSMakeRange(NSNotFound, 0);
self.undoActive = false;
return self;
}
@end

@interface ScintillaNSView : wxNSView <NSTextInputClient>
@property (nonatomic, readonly) wxStyledTextCtrl *stc;
@property (nonatomic, readonly) IMEState *state;
@end

@implementation ScintillaNSView
- (Class)superclass {
return [wxNSView superclass];
}

- (wxStyledTextCtrl *)stc {
return static_cast<wxStyledTextCtrl *>(wxWidgetImpl::FindFromWXWidget(self)->GetWXPeer());
}

- (IMEState *)state {
return objc_getAssociatedObject(self, [IMEState class]);
}

- (void)invalidate {
self.state.markedRange = NSMakeRange(NSNotFound, 0);
[self.inputContext discardMarkedText];
}

#pragma mark - NSTextInputClient

- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange
actualRange:(NSRangePointer)actualRange
{
return nil;
}

- (NSUInteger)characterIndexForPoint:(NSPoint)point {
return self.stc->PositionFromPoint(wxPoint(point.x, point.y));
}

- (BOOL)drawsVerticallyForCharacterAtIndex:(NSUInteger)charIndex {
return NO;
}

- (NSRect)firstRectForCharacterRange:(NSRange)range
actualRange:(NSRangePointer)actualRange
{
auto stc = self.stc;
int line = stc->LineFromPosition(range.location);
int height = stc->TextHeight(line);
auto pt = stc->PointFromPosition(range.location);

int width = 0;
if (range.length > 0) {
// If the end of the range is on the next line, the range should be
// truncated to the current line and actualRange should be set to the
// truncated range
int end_line = stc->LineFromPosition(range.location + range.length);
if (end_line > line) {
range.length = stc->PositionFromLine(line + 1) - 1 - range.location;
*actualRange = range;
}

auto end_pt = stc->PointFromPosition(range.location + range.length);
width = end_pt.x - pt.x;
}

auto rect = NSMakeRect(pt.x, pt.y, width, height);
rect = [self convertRect:rect toView:nil];
return [self.window convertRectToScreen:rect];
}

- (BOOL)hasMarkedText {
return self.state.markedRange.length > 0;
}

- (void)insertText:(id)str replacementRange:(NSRange)replacementRange {
[self unmarkText];
[super insertText:str replacementRange:replacementRange];
}

- (NSRange)markedRange {
return self.state.markedRange;
}

- (NSRange)selectedRange {
long from = 0, to = 0;
self.stc->GetSelection(&from, &to);
return NSMakeRange(from, to - from);
}

- (void)setMarkedText:(id)str
selectedRange:(NSRange)range
replacementRange:(NSRange)replacementRange
{
if ([str isKindOfClass:[NSAttributedString class]])
str = [str string];

auto stc = self.stc;
auto state = self.state;

int pos = stc->GetInsertionPoint();
if (state.markedRange.length > 0) {
pos = state.markedRange.location;
stc->DeleteRange(pos, state.markedRange.length);
stc->SetSelection(pos, pos);
} else {
state.undoActive = stc->GetUndoCollection();
if (state.undoActive)
stc->SetUndoCollection(false);
}

auto utf8 = [str UTF8String];
auto utf8len = strlen(utf8);
stc->AddTextRaw(utf8, utf8len);

state.markedRange = NSMakeRange(pos, utf8len);

stc->SetIndicatorCurrent(1);
stc->IndicatorFillRange(pos, utf8len);

// Re-enable undo if we got a zero-length string as that means we're done
if (!utf8len && state.undoActive)
stc->SetUndoCollection(true);
else {
int start = pos;
// Range is in utf-16 code units
if (range.location > 0)
start += [[str substringToIndex:range.location] lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
int length = [[str substringWithRange:range] lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
stc->SetSelection(start, start + length);
}
}

- (void)unmarkText {
auto state = self.state;
if (state.markedRange.length > 0) {
self.stc->DeleteRange(state.markedRange.location, state.markedRange.length);
state.markedRange = NSMakeRange(NSNotFound, 0);
if (state.undoActive)
self.stc->SetUndoCollection(true);
}
}

- (NSArray *)validAttributesForMarkedText {
return @[];
}
@end

namespace osx { namespace ime {
void inject(wxStyledTextCtrl *ctrl) {
id view = (id)ctrl->GetHandle();
object_setClass(view, [ScintillaNSView class]);

auto state = [IMEState new];
objc_setAssociatedObject(view, [IMEState class], state,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[state release];
}

void invalidate(wxStyledTextCtrl *ctrl) {
[(ScintillaNSView *)ctrl->GetHandle() invalidate];
}

bool process_key_event(wxStyledTextCtrl *ctrl, wxKeyEvent &evt) {
if (evt.GetModifiers() != 0) return false;
if (evt.GetKeyCode() != WXK_RETURN && evt.GetKeyCode() != WXK_TAB) return false;
if (![(ScintillaNSView *)ctrl->GetHandle() hasMarkedText]) return false;

evt.Skip();
return true;
}

} }
4 changes: 3 additions & 1 deletion src/subs_edit_box.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
#include "text_selection_controller.h"
#include "timeedit_ctrl.h"
#include "tooltip_manager.h"
#include "utils.h"
#include "validators.h"

#include <libaegisub/character_count.h>
Expand Down Expand Up @@ -448,7 +449,8 @@ void SubsEditBox::UpdateFrameTiming(agi::vfr::Framerate const& fps) {
}

void SubsEditBox::OnKeyDown(wxKeyEvent &event) {
hotkey::check("Subtitle Edit Box", c, event);
if (!osx::ime::process_key_event(edit_ctrl, event))
hotkey::check("Subtitle Edit Box", c, event);
}

void SubsEditBox::OnChange(wxStyledTextEvent &event) {
Expand Down
8 changes: 8 additions & 0 deletions src/subs_edit_ctrl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ SubsTextEditCtrl::SubsTextEditCtrl(wxWindow* parent, wxSize wsize, long style, a
, thesaurus(agi::make_unique<Thesaurus>())
, context(context)
{
osx::ime::inject(this);

// Set properties
SetWrapMode(wxSTC_WRAP_WORD);
SetMarginWidth(1,0);
Expand Down Expand Up @@ -204,6 +206,7 @@ void SubsTextEditCtrl::OnLoseFocus(wxFocusEvent &event) {
}

void SubsTextEditCtrl::OnKeyDown(wxKeyEvent &event) {
if (osx::ime::process_key_event(this, event)) return;
event.Skip();

// Workaround for wxSTC eating tabs.
Expand Down Expand Up @@ -267,6 +270,10 @@ void SubsTextEditCtrl::SetStyles() {
// Misspelling indicator
IndicatorSetStyle(0,wxSTC_INDIC_SQUIGGLE);
IndicatorSetForeground(0,wxColour(255,0,0));

// IME pending text indicator
IndicatorSetStyle(1, wxSTC_INDIC_PLAIN);
IndicatorSetUnder(1, true);
}

void SubsTextEditCtrl::UpdateStyle() {
Expand Down Expand Up @@ -331,6 +338,7 @@ void SubsTextEditCtrl::UpdateCallTip() {
}

void SubsTextEditCtrl::SetTextTo(std::string const& text) {
osx::ime::invalidate(this);
SetEvtHandlerEnabled(false);
Freeze();

Expand Down
7 changes: 7 additions & 0 deletions src/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,13 @@ int RetinaHelper::GetScaleFactor() const {
return 1;
#endif
}

// OS X implementation in scintilla_ime.mm
namespace osx { namespace ime {
void inject(wxStyledTextCtrl *) { }
void invalidate(wxStyledTextCtrl *) { }
bool process_key_event(wxStyledTextCtrl *, wxKeyEvent&) { return false; }
} }
#endif

wxString FontFace(std::string opt_prefix) {
Expand Down
9 changes: 9 additions & 0 deletions src/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,13 @@ namespace osx {
bool activate_top_window_other_than(wxFrame *wx);
// Bring all windows to the front, maintaining relative z-order
void bring_to_front();

namespace ime {
/// Inject the IME helper into the given wxSTC
void inject(wxStyledTextCtrl *ctrl);
/// Invalidate any pending text from the IME
void invalidate(wxStyledTextCtrl *ctrl);
/// Give the IME a chance to process a key event and return whether it did
bool process_key_event(wxStyledTextCtrl *ctrl, wxKeyEvent &);
}
}

0 comments on commit fac664a

Please sign in to comment.