1 /* Reverse Engineer's Hex Editor
2  * Copyright (C) 2018 Daniel Collins <solemnwarning@solemnwarning.net>
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License version 2 as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but WITHOUT
9  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
11  * more details.
12  *
13  * You should have received a copy of the GNU General Public License along with
14  * this program; if not, write to the Free Software Foundation, Inc., 51
15  * Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16 */
17 
18 #include "platform.hpp"
19 #include <assert.h>
20 #include <wx/clipbrd.h>
21 #include <wx/dcbuffer.h>
22 
23 #include "App.hpp"
24 #include "CodeCtrl.hpp"
25 
26 enum {
27 	ID_SELECT_TIMER = 1,
28 };
29 
BEGIN_EVENT_TABLE(REHex::CodeCtrl,wxControl)30 BEGIN_EVENT_TABLE(REHex::CodeCtrl, wxControl)
31 	EVT_PAINT(REHex::CodeCtrl::OnPaint)
32 	EVT_ERASE_BACKGROUND(REHex::CodeCtrl::OnErase)
33 	EVT_SIZE(REHex::CodeCtrl::OnSize)
34 	EVT_SCROLLWIN(REHex::CodeCtrl::OnScroll)
35 	EVT_MOUSEWHEEL(REHex::CodeCtrl::OnWheel)
36 	EVT_CHAR(REHex::CodeCtrl::OnChar)
37 	EVT_LEFT_DOWN(REHex::CodeCtrl::OnLeftDown)
38 	EVT_LEFT_UP(REHex::CodeCtrl::OnLeftUp)
39 	EVT_RIGHT_DOWN(REHex::CodeCtrl::OnRightDown)
40 	EVT_MENU(wxID_COPY, REHex::CodeCtrl::OnCopy)
41 	EVT_MENU(wxID_SELECTALL, REHex::CodeCtrl::OnSelectAll)
42 	EVT_MOTION(REHex::CodeCtrl::OnMotion)
43 	EVT_TIMER(ID_SELECT_TIMER, REHex::CodeCtrl::OnSelectTick)
44 END_EVENT_TABLE()
45 
46 REHex::CodeCtrl::CodeCtrl(wxWindow *parent, wxWindowID id):
47 	wxControl(parent, id, wxDefaultPosition, wxDefaultSize, (wxVSCROLL | wxHSCROLL | wxWANTS_CHARS)),
48 	font(wxFontInfo().FaceName(wxGetApp().get_font_name())),
49 	max_line_width(0),
50 	offset_display_base(OFFSET_BASE_HEX),
51 	offset_display_upper_bound(0xFFFFFFFF),
52 	scroll_xoff(0), scroll_xoff_max(0),
53 	scroll_yoff(0), scroll_yoff_max(0),
54 	wheel_vert_accum(0),
55 	wheel_horiz_accum(0),
56 	mouse_selecting(false),
57 	mouse_selecting_timer(this, ID_SELECT_TIMER),
58 	selection_begin(-1, -1),
59 	selection_end(-1, -1)
60 {
61 	App &app = wxGetApp();
62 
63 	app.Bind(FONT_SIZE_ADJUSTMENT_CHANGED, &REHex::CodeCtrl::OnFontSizeAdjustmentChanged, this);
64 
65 	int font_size_adjustment = app.get_font_size_adjustment();
66 
67 	while(font_size_adjustment > 0) { font.MakeLarger(); --font_size_adjustment; }
68 	while(font_size_adjustment < 0) { font.MakeSmaller(); ++font_size_adjustment; }
69 
70 	assert(font.IsFixedWidth());
71 
72 	wxClientDC dc(this);
73 	dc.SetFont(font);
74 
75 	wxSize char_extent = dc.GetTextExtent("X");
76 	font_width  = char_extent.GetWidth();
77 	font_height = char_extent.GetHeight();
78 
79 	std::string offset_str = format_offset(0, offset_display_base, offset_display_upper_bound);
80 	code_xoff = dc.GetTextExtent(offset_str + "  ").GetWidth();
81 }
82 
~CodeCtrl()83 REHex::CodeCtrl::~CodeCtrl()
84 {
85 	wxGetApp().Unbind(FONT_SIZE_ADJUSTMENT_CHANGED, &REHex::CodeCtrl::OnFontSizeAdjustmentChanged, this);
86 }
87 
append_line(off_t offset,const std::string & text,bool active)88 void REHex::CodeCtrl::append_line(off_t offset, const std::string &text, bool active)
89 {
90 	wxClientDC dc(this);
91 	dc.SetFont(font);
92 
93 	/* GetTextExtent() doesn't seem to handle tabs correctly, so we expand
94 	 * them into spaces.
95 	*/
96 
97 	std::string text_no_tabs = text;
98 	static const int TAB_WIDTH = 8;
99 
100 	for(size_t p = 0; (p = text_no_tabs.find('\t', p)) != std::string::npos;)
101 	{
102 		size_t n_spaces = TAB_WIDTH - (p % TAB_WIDTH);
103 		text_no_tabs.replace(p, 1, n_spaces, ' ');
104 	}
105 
106 	int line_width = code_xoff + dc.GetTextExtent(text_no_tabs).GetWidth();
107 	if(max_line_width < line_width)
108 	{
109 		max_line_width = line_width;
110 	}
111 
112 	lines.emplace_back(offset, text_no_tabs, active);
113 
114 	update_scrollbars();
115 	Refresh();
116 }
117 
clear()118 void REHex::CodeCtrl::clear()
119 {
120 	selection_begin = CodeCharRef(-1, -1);
121 	selection_end   = CodeCharRef(-1, -1);
122 
123 	max_line_width = 0;
124 	lines.clear();
125 
126 	update_scrollbars();
127 	Refresh();
128 }
129 
center_line(int line)130 void REHex::CodeCtrl::center_line(int line)
131 {
132 	wxSize client_size = GetClientSize();
133 
134 	scroll_yoff = (line * font_height) - (client_size.GetHeight() / 2);
135 
136 	if(scroll_yoff < 0)
137 	{
138 		scroll_yoff = 0;
139 	}
140 	else if(scroll_yoff > scroll_yoff_max)
141 	{
142 		scroll_yoff = scroll_yoff_max;
143 	}
144 
145 	scroll_xoff = 0;
146 
147 	update_scrollbars();
148 	Refresh();
149 }
150 
update_scrollbars()151 void REHex::CodeCtrl::update_scrollbars()
152 {
153 	wxSize client_size = GetClientSize();
154 
155 	int virt_height = lines.size() * font_height;
156 	if(virt_height > client_size.GetHeight())
157 	{
158 		scroll_yoff_max = virt_height - client_size.GetHeight();
159 		if(scroll_yoff > scroll_yoff_max)
160 		{
161 			scroll_yoff = scroll_yoff_max;
162 		}
163 
164 		SetScrollbar(wxVERTICAL, scroll_yoff, client_size.GetHeight(), virt_height);
165 	}
166 	else{
167 		scroll_yoff_max = 0;
168 		scroll_yoff     = 0;
169 
170 		SetScrollbar(wxVERTICAL, 0, 0, 0);
171 	}
172 
173 	if(max_line_width > client_size.GetWidth())
174 	{
175 		scroll_xoff_max = max_line_width - client_size.GetWidth();
176 		if(scroll_xoff > scroll_xoff_max)
177 		{
178 			scroll_xoff = scroll_xoff_max;
179 		}
180 
181 		SetScrollbar(wxHORIZONTAL, scroll_xoff, client_size.GetWidth(), max_line_width);
182 	}
183 	else{
184 		scroll_xoff_max = 0;
185 		scroll_xoff     = 0;
186 
187 		SetScrollbar(wxHORIZONTAL, 0, 0, 0);
188 	}
189 }
190 
update_widths()191 void REHex::CodeCtrl::update_widths()
192 {
193 	wxClientDC dc(this);
194 	dc.SetFont(font);
195 
196 	std::string offset_str = format_offset(0, offset_display_base, offset_display_upper_bound);
197 	code_xoff = dc.GetTextExtent(offset_str + "  ").GetWidth();
198 
199 	max_line_width = 0;
200 
201 	for(auto l = lines.begin(); l != lines.end(); ++l)
202 	{
203 		int line_width = code_xoff + dc.GetTextExtent(l->text).GetWidth();
204 		if(max_line_width < line_width)
205 		{
206 			max_line_width = line_width;
207 		}
208 	}
209 }
210 
char_near_abs_xy(int abs_x,int abs_y)211 REHex::CodeCtrl::CodeCharRef REHex::CodeCtrl::char_near_abs_xy(int abs_x, int abs_y)
212 {
213 	if(lines.empty())
214 	{
215 		return CodeCharRef(-1, -1);
216 	}
217 
218 	int line_idx = std::min((abs_y / font_height), (int)(lines.size() - 1));
219 	const Line &line = *(std::next(lines.begin(), line_idx));
220 
221 	int col = 0;
222 
223 	wxClientDC dc(this);
224 	dc.SetFont(font);
225 
226 	while((code_xoff + dc.GetTextExtent(std::string((col + 1), 'X')).GetWidth()) < abs_x && col < (int)(line.text.length()))
227 	{
228 		++col;
229 	}
230 
231 	return CodeCharRef(line_idx, col);
232 }
233 
char_near_rel_xy(int rel_x,int rel_y)234 REHex::CodeCtrl::CodeCharRef REHex::CodeCtrl::char_near_rel_xy(int rel_x, int rel_y)
235 {
236 	return char_near_abs_xy((rel_x + scroll_xoff), (rel_y + scroll_yoff));
237 }
238 
copy_selection()239 void REHex::CodeCtrl::copy_selection()
240 {
241 	if(selection_end > selection_begin && wxTheClipboard->Open())
242 	{
243 		std::string copy_text;
244 
245 		for(int i = selection_begin.first; i <= selection_end.first; ++i)
246 		{
247 			assert(i >= 0);
248 			assert((unsigned)(i) < lines.size());
249 
250 			const std::string &line_text = lines[i].text;
251 
252 			if(i > selection_begin.first)
253 			{
254 				copy_text += '\n';
255 			}
256 
257 			int substr_off = (i == selection_begin.first ? selection_begin.second : 0);
258 			assert(substr_off >= 0);
259 			assert((unsigned)(substr_off) <= line_text.length());
260 
261 			int substr_len = (i == selection_end.first ? selection_end.second : line_text.length()) - substr_off;
262 			assert(substr_len >= 0);
263 			assert((unsigned)(substr_off + substr_len) <= line_text.length());
264 
265 			copy_text += line_text.substr(substr_off, substr_len);
266 		}
267 
268 		wxTheClipboard->SetData(new wxTextDataObject(copy_text));
269 		wxTheClipboard->Close();
270 	}
271 }
272 
select_all()273 void REHex::CodeCtrl::select_all()
274 {
275 	if(!lines.empty())
276 	{
277 		selection_begin = CodeCharRef(0, 0);
278 		selection_end = CodeCharRef((lines.size() - 1), lines.back().text.length());
279 
280 		Refresh();
281 	}
282 }
283 
set_offset_display(REHex::OffsetBase offset_display_base,off_t offset_display_upper_bound)284 void REHex::CodeCtrl::set_offset_display(REHex::OffsetBase offset_display_base, off_t offset_display_upper_bound)
285 {
286 	this->offset_display_base        = offset_display_base;
287 	this->offset_display_upper_bound = offset_display_upper_bound;
288 
289 	update_widths();
290 	update_scrollbars();
291 	Refresh();
292 }
293 
OnPaint(wxPaintEvent & event)294 void REHex::CodeCtrl::OnPaint(wxPaintEvent &event)
295 {
296 	wxSize client_size = GetClientSize();
297 
298 	wxBufferedPaintDC dc(this);
299 
300 	dc.SetFont(font);
301 	dc.SetBackground(*wxWHITE_BRUSH);
302 	dc.SetBackgroundMode(wxTRANSPARENT);
303 
304 	dc.Clear();
305 
306 	int x = -scroll_xoff;
307 	int y = -scroll_yoff;
308 
309 	int line_idx = 0;
310 	for(auto line = lines.begin(); line != lines.end(); ++line, y += font_height, ++line_idx)
311 	{
312 		if((y + font_height) <= 0 || y >= client_size.GetHeight())
313 		{
314 			/* Line not visible, no point rendering it. */
315 			continue;
316 		}
317 
318 		wxColour fg_colour;
319 		wxColour bg_colour;
320 
321 		int line_x = x + code_xoff;
322 		std::string pending;
323 
324 		auto flush = [&dc, &line_x, &pending, &y]()
325 		{
326 			if(!pending.empty())
327 			{
328 				dc.DrawText(pending, line_x, y);
329 				line_x += dc.GetTextExtent(pending).GetWidth();
330 
331 				pending.clear();
332 			}
333 		};
334 
335 		auto set = [&dc, &fg_colour, &bg_colour, &flush](const wxColour &fg, const wxColour &bg, bool force = false)
336 		{
337 			if(fg != fg_colour || bg != bg_colour || force)
338 			{
339 				flush();
340 
341 				dc.SetTextForeground(fg_colour = fg);
342 				dc.SetTextBackground(bg_colour = bg);
343 				dc.SetBackgroundMode(bg_colour == *wxWHITE ? wxTRANSPARENT : wxSOLID);
344 			}
345 		};
346 
347 		if(line->active)
348 		{
349 			set(*wxRED, *wxWHITE, true);
350 		}
351 		else{
352 			set(*wxBLACK, *wxWHITE, true);
353 		}
354 
355 		std::string offset_str = format_offset(line->offset, offset_display_base, offset_display_upper_bound);
356 		dc.DrawText(offset_str.c_str(), x, y);
357 
358 		for(size_t c = 0; c < line->text.length(); ++c)
359 		{
360 			if(selection_begin <= CodeCharRef(line_idx, c) && selection_end > CodeCharRef(line_idx, c))
361 			{
362 				set(*wxWHITE, *wxBLUE);
363 			}
364 			else if(line->active)
365 			{
366 				set(*wxRED, *wxWHITE);
367 			}
368 			else{
369 				set(*wxBLACK, *wxWHITE);
370 			}
371 
372 			pending.push_back(line->text[c]);
373 		}
374 
375 		flush();
376 	}
377 }
378 
OnErase(wxEraseEvent & event)379 void REHex::CodeCtrl::OnErase(wxEraseEvent& event)
380 {
381 	// Left blank to disable erase
382 }
383 
OnSize(wxSizeEvent & event)384 void REHex::CodeCtrl::OnSize(wxSizeEvent &event)
385 {
386 	update_scrollbars();
387 }
388 
OnFontSizeAdjustmentChanged(FontSizeAdjustmentEvent & event)389 void REHex::CodeCtrl::OnFontSizeAdjustmentChanged(FontSizeAdjustmentEvent &event)
390 {
391 	font = wxFont(wxFontInfo().FaceName(wxGetApp().get_font_name()));
392 
393 	for(int i = 0; i < event.font_size_adjustment; ++i) { font.MakeLarger(); }
394 	for(int i = 0; i > event.font_size_adjustment; --i) { font.MakeSmaller(); }
395 
396 	assert(font.IsFixedWidth());
397 
398 	wxClientDC dc(this);
399 	dc.SetFont(font);
400 
401 	wxSize char_extent = dc.GetTextExtent("X");
402 	font_width  = char_extent.GetWidth();
403 	font_height = char_extent.GetHeight();
404 
405 	update_widths();
406 	update_scrollbars();
407 	Refresh();
408 
409 	event.Skip();
410 }
411 
OnScroll(wxScrollWinEvent & event)412 void REHex::CodeCtrl::OnScroll(wxScrollWinEvent &event)
413 {
414 	wxEventType type = event.GetEventType();
415 	int orientation  = event.GetOrientation();
416 
417 	wxSize client_size = GetClientSize();
418 
419 	if(orientation == wxVERTICAL)
420 	{
421 		if(type == wxEVT_SCROLLWIN_THUMBTRACK || type == wxEVT_SCROLLWIN_THUMBRELEASE)
422 		{
423 			scroll_yoff = event.GetPosition();
424 		}
425 		else if(event.GetEventType() == wxEVT_SCROLLWIN_TOP)
426 		{
427 			scroll_yoff = 0;
428 		}
429 		else if(event.GetEventType() == wxEVT_SCROLLWIN_BOTTOM)
430 		{
431 			scroll_yoff = scroll_yoff_max;
432 		}
433 		else if(event.GetEventType() == wxEVT_SCROLLWIN_LINEUP)
434 		{
435 			--scroll_yoff;
436 		}
437 		else if(event.GetEventType() == wxEVT_SCROLLWIN_LINEDOWN)
438 		{
439 			++scroll_yoff;
440 		}
441 		else if(event.GetEventType() == wxEVT_SCROLLWIN_PAGEUP)
442 		{
443 			scroll_yoff -= client_size.GetHeight();
444 		}
445 		else if(event.GetEventType() == wxEVT_SCROLLWIN_PAGEDOWN)
446 		{
447 			scroll_yoff += client_size.GetHeight();
448 		}
449 
450 		if(scroll_yoff < 0)
451 		{
452 			scroll_yoff = 0;
453 		}
454 		else if(scroll_yoff > scroll_yoff_max)
455 		{
456 			scroll_yoff = scroll_yoff_max;
457 		}
458 
459 		SetScrollPos(wxVERTICAL, scroll_yoff);
460 		Refresh();
461 	}
462 	else if(orientation == wxHORIZONTAL)
463 	{
464 		if(type == wxEVT_SCROLLWIN_THUMBTRACK || type == wxEVT_SCROLLWIN_THUMBRELEASE)
465 		{
466 			scroll_xoff = event.GetPosition();
467 		}
468 		else if(event.GetEventType() == wxEVT_SCROLLWIN_TOP)
469 		{
470 			scroll_xoff = 0;
471 		}
472 		else if(event.GetEventType() == wxEVT_SCROLLWIN_BOTTOM)
473 		{
474 			scroll_xoff = scroll_xoff_max;
475 		}
476 		else if(event.GetEventType() == wxEVT_SCROLLWIN_LINEUP)
477 		{
478 			scroll_xoff -= font_width;
479 		}
480 		else if(event.GetEventType() == wxEVT_SCROLLWIN_LINEDOWN)
481 		{
482 			scroll_xoff += font_width;
483 		}
484 		else if(event.GetEventType() == wxEVT_SCROLLWIN_PAGEUP)
485 		{
486 			scroll_xoff -= client_size.GetWidth();
487 		}
488 		else if(event.GetEventType() == wxEVT_SCROLLWIN_PAGEDOWN)
489 		{
490 			scroll_xoff += client_size.GetWidth();
491 		}
492 
493 		if(scroll_xoff < 0)
494 		{
495 			scroll_xoff = 0;
496 		}
497 		else if(scroll_xoff > scroll_xoff_max)
498 		{
499 			scroll_xoff = scroll_xoff_max;
500 		}
501 
502 		SetScrollPos(wxHORIZONTAL, scroll_xoff);
503 		Refresh();
504 	}
505 }
506 
OnWheel(wxMouseEvent & event)507 void REHex::CodeCtrl::OnWheel(wxMouseEvent &event)
508 {
509 	wxMouseWheelAxis axis = event.GetWheelAxis();
510 	int delta             = event.GetWheelDelta();
511 	int ticks_per_delta   = event.GetLinesPerAction();
512 
513 	if(axis == wxMOUSE_WHEEL_VERTICAL)
514 	{
515 		ticks_per_delta *= font_height;
516 
517 		wheel_vert_accum += event.GetWheelRotation();
518 
519 		scroll_yoff -= (wheel_vert_accum / delta) * ticks_per_delta;
520 
521 		wheel_vert_accum = (wheel_vert_accum % delta);
522 
523 		if(scroll_yoff < 0)
524 		{
525 			scroll_yoff = 0;
526 		}
527 		else if(scroll_yoff > scroll_yoff_max)
528 		{
529 			scroll_yoff = scroll_yoff_max;
530 		}
531 
532 		SetScrollPos(wxVERTICAL, scroll_yoff);
533 		Refresh();
534 	}
535 	else if(axis == wxMOUSE_WHEEL_HORIZONTAL)
536 	{
537 		ticks_per_delta *= font_width;
538 
539 		wheel_horiz_accum += event.GetWheelRotation();
540 
541 		scroll_xoff += (wheel_horiz_accum / delta) * ticks_per_delta;
542 
543 		wheel_horiz_accum = (wheel_horiz_accum % delta);
544 
545 		if(scroll_xoff < 0)
546 		{
547 			scroll_xoff = 0;
548 		}
549 		else if(scroll_xoff > scroll_xoff_max)
550 		{
551 			scroll_xoff = scroll_xoff_max;
552 		}
553 
554 		SetScrollPos(wxHORIZONTAL, scroll_xoff);
555 		Refresh();
556 	}
557 }
558 
OnChar(wxKeyEvent & event)559 void REHex::CodeCtrl::OnChar(wxKeyEvent &event)
560 {
561 	int key       = event.GetKeyCode();
562 	int modifiers = event.GetModifiers();
563 
564 	if((modifiers & wxMOD_CONTROL) && key == WXK_CONTROL_A)
565 	{
566 		select_all();
567 	}
568 	else if((modifiers & wxMOD_CONTROL) && key == WXK_CONTROL_C)
569 	{
570 		copy_selection();
571 	}
572 	else{
573 		/* Not for us. Continue propagation. */
574 		event.Skip();
575 	}
576 }
577 
OnLeftDown(wxMouseEvent & event)578 void REHex::CodeCtrl::OnLeftDown(wxMouseEvent &event)
579 {
580 	int mouse_x = event.GetX();
581 	int mouse_y = event.GetY();
582 
583 	mouse_selecting_from = char_near_rel_xy(mouse_x, mouse_y);
584 	if(mouse_selecting_from.first >= 0)
585 	{
586 		mouse_selecting = true;
587 
588 		CaptureMouse();
589 		mouse_selecting_timer.Start(MOUSE_SELECT_INTERVAL, wxTIMER_CONTINUOUS);
590 
591 		OnMotionTick(mouse_x, mouse_y);
592 	}
593 
594 	Refresh();
595 
596 	/* We take focus when clicked. */
597 	SetFocus();
598 }
599 
OnLeftUp(wxMouseEvent & event)600 void REHex::CodeCtrl::OnLeftUp(wxMouseEvent &event)
601 {
602 	if(mouse_selecting)
603 	{
604 		mouse_selecting_timer.Stop();
605 		ReleaseMouse();
606 
607 		mouse_selecting = false;
608 	}
609 }
610 
OnRightDown(wxMouseEvent & event)611 void REHex::CodeCtrl::OnRightDown(wxMouseEvent &event)
612 {
613 	/* If the user right clicks while selecting, and then releases the left button over the
614 	 * menu, we never receive the EVT_LEFT_UP event. Release the mouse and cancel the selection
615 	 * now, else we wind up keeping the mouse grabbed and stop it interacting with any other
616 	 * windows...
617 	*/
618 
619 	if(mouse_selecting)
620 	{
621 		mouse_selecting_timer.Stop();
622 		ReleaseMouse();
623 
624 		mouse_selecting = false;
625 	}
626 
627 	wxMenu menu;
628 
629 	menu.Append(wxID_COPY,  "&Copy");
630 	menu.Enable(wxID_COPY, (selection_begin < selection_end));
631 
632 	menu.AppendSeparator();
633 
634 	menu.Append(wxID_SELECTALL, "Select &All");
635 
636 	PopupMenu(&menu);
637 
638 	/* We take focus when clicked. */
639 	SetFocus();
640 }
641 
OnCopy(wxCommandEvent & event)642 void REHex::CodeCtrl::OnCopy(wxCommandEvent &event)
643 {
644 	copy_selection();
645 }
646 
OnSelectAll(wxCommandEvent & event)647 void REHex::CodeCtrl::OnSelectAll(wxCommandEvent &event)
648 {
649 	select_all();
650 }
651 
OnMotion(wxMouseEvent & event)652 void REHex::CodeCtrl::OnMotion(wxMouseEvent &event)
653 {
654 	OnMotionTick(event.GetX(), event.GetY());
655 }
656 
OnSelectTick(wxTimerEvent & event)657 void REHex::CodeCtrl::OnSelectTick(wxTimerEvent &event)
658 {
659 	wxPoint window_pos = GetScreenPosition();
660 	wxPoint mouse_pos  = wxGetMousePosition();
661 
662 	OnMotionTick((mouse_pos.x - window_pos.x), (mouse_pos.y - window_pos.y));
663 }
664 
OnMotionTick(int mouse_x,int mouse_y)665 void REHex::CodeCtrl::OnMotionTick(int mouse_x, int mouse_y)
666 {
667 	if(mouse_selecting)
668 	{
669 		mouse_selecting_to = char_near_rel_xy(mouse_x, mouse_y);
670 
671 		if(mouse_selecting_to > mouse_selecting_from)
672 		{
673 			selection_begin = mouse_selecting_from;
674 			selection_end   = mouse_selecting_to;
675 		}
676 		else if(mouse_selecting_to < mouse_selecting_from)
677 		{
678 			selection_begin = mouse_selecting_to;
679 			selection_end   = mouse_selecting_from;
680 		}
681 		else{
682 			selection_begin = CodeCharRef(-1, -1);
683 			selection_end   = CodeCharRef(-1, -1);
684 		}
685 
686 		Refresh();
687 	}
688 }
689