1 /* Reverse Engineer's Hex Editor
2  * Copyright (C) 2017-2021 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 #ifndef REHEX_DOCUMENTCTRL_HPP
19 #define REHEX_DOCUMENTCTRL_HPP
20 
21 #include <functional>
22 #include <jansson.h>
23 #include <list>
24 #include <memory>
25 #include <stdint.h>
26 #include <utility>
27 #include <vector>
28 #include <wx/dataobj.h>
29 #include <wx/wx.h>
30 
31 #include "buffer.hpp"
32 #include "ByteRangeSet.hpp"
33 #include "document.hpp"
34 #include "Events.hpp"
35 #include "LRUCache.hpp"
36 #include "NestedOffsetLengthMap.hpp"
37 #include "Palette.hpp"
38 #include "SharedDocumentPointer.hpp"
39 #include "util.hpp"
40 
41 namespace REHex {
42 	class DocumentCtrl: public wxControl {
43 		public:
44 			/**
45 			 * @brief An on-screen rectangle in the DocumentCtrl.
46 			*/
47 			struct Rect
48 			{
49 				int x;      /**< X co-ordinate, in pixels. */
50 				int64_t y;  /**< Y co-ordinate, in lines. */
51 
52 				int w;      /**< Width, in pixels. */
53 				int64_t h;  /**< Height, in lines. */
54 
RectREHex::DocumentCtrl::Rect55 				Rect(): x(-1), y(-1), w(-1), h(-1) {}
RectREHex::DocumentCtrl::Rect56 				Rect(int x, int64_t y, int w, int64_t h): x(x), y(y), w(w), h(h) {}
57 			};
58 
59 			class Region
60 			{
61 				protected:
62 					int64_t y_offset; /* First on-screen line in region */
63 					int64_t y_lines;  /* Number of on-screen lines in region */
64 
65 					int indent_depth;  /* Indentation depth */
66 					int indent_final;  /* Number of inner indentation levels we are the final region in */
67 
68 				public:
69 					const off_t indent_offset;
70 					const off_t indent_length;
71 
72 					virtual ~Region();
73 
74 					enum StateFlag
75 					{
76 						IDLE       = 0,
77 						PROCESSING = (1 << 0),
78 
79 						WIDTH_CHANGE  = (1 << 1),
80 						HEIGHT_CHANGE = (1 << 2),
81 						REDRAW        = (1 << 3),
82 					};
83 
84 					virtual unsigned int check();
85 
86 				protected:
87 					Region(off_t indent_offset, off_t indent_length);
88 
89 					virtual int calc_width(REHex::DocumentCtrl &doc);
90 					virtual void calc_height(REHex::DocumentCtrl &doc, wxDC &dc) = 0;
91 
92 					/* Draw this region on the screen.
93 					 *
94 					 * doc - The parent Document object
95 					 * dc  - The wxDC to draw in
96 					 * x,y - The top-left co-ordinates of this Region in the DC (MAY BE NEGATIVE)
97 					 *
98 					 * The implementation MAY skip rendering outside of the client area
99 					 * of the DC to improve performance.
100 					*/
101 					virtual void draw(REHex::DocumentCtrl &doc, wxDC &dc, int x, int64_t y) = 0;
102 
103 					virtual wxCursor cursor_for_point(REHex::DocumentCtrl &doc, int x, int64_t y_lines, int y_px);
104 
105 					void draw_container(REHex::DocumentCtrl &doc, wxDC &dc, int x, int64_t y);
106 					void draw_full_height_line(DocumentCtrl *doc_ctrl, wxDC &dc, int x, int64_t y);
107 
108 					struct Highlight
109 					{
110 						public:
111 							const bool enable;
112 
113 							const Palette::ColourIndex fg_colour_idx;
114 							const Palette::ColourIndex bg_colour_idx;
115 							const bool strong;
116 
HighlightREHex::DocumentCtrl::Region::Highlight117 							Highlight(Palette::ColourIndex fg_colour_idx, Palette::ColourIndex bg_colour_idx, bool strong):
118 								enable(true),
119 								fg_colour_idx(fg_colour_idx),
120 								bg_colour_idx(bg_colour_idx),
121 								strong(strong) {}
122 
123 						protected:
HighlightREHex::DocumentCtrl::Region::Highlight124 							Highlight():
125 								enable(false),
126 								fg_colour_idx(Palette::PAL_INVALID),
127 								bg_colour_idx(Palette::PAL_INVALID),
128 								strong(false) {}
129 					};
130 
131 					struct NoHighlight: Highlight
132 					{
NoHighlightREHex::DocumentCtrl::Region::NoHighlight133 						NoHighlight(): Highlight() {}
134 					};
135 
136 					static void draw_hex_line(DocumentCtrl *doc_ctrl, wxDC &dc, int x, int y, const unsigned char *data, size_t data_len, unsigned int pad_bytes, off_t base_off, bool alternate_row, const std::function<Highlight(off_t)> &highlight_at_off);
137 					static void draw_ascii_line(DocumentCtrl *doc_ctrl, wxDC &dc, int x, int y, const unsigned char *data, size_t data_len, size_t data_extra_pre, size_t data_extra_post, off_t alignment_hint, unsigned int pad_bytes, off_t base_off, bool alternate_row, const std::function<Highlight(off_t)> &highlight_at_off);
138 
139 					/**
140 					 * @brief Calculate offset of byte at X co-ordinate.
141 					 *
142 					 * Calculates the offset of the byte at the given X
143 					 * co-ordinate in a line drawn with draw_hex_line(). Returns
144 					 * -1 if the co-ordinate is negative or falls between byte
145 					 * groups.
146 					*/
147 					static int offset_at_x_hex(DocumentCtrl *doc_ctrl, int rel_x);
148 
149 					/**
150 					 * @brief Calculate offset of byte near X co-ordinate.
151 					 *
152 					 * Calculates the offset of the byte nearest the given X
153 					 * co-ordinate in a line drawn with draw_hex_line(). Returns
154 					 * -1 if the co-ordinate is negative.
155 					*/
156 					static int offset_near_x_hex(DocumentCtrl *doc_ctrl, int rel_x);
157 
158 				friend DocumentCtrl;
159 			};
160 
161 			class GenericDataRegion: public Region
162 			{
163 				protected:
164 					GenericDataRegion(off_t d_offset, off_t d_length, off_t indent_offset);
165 
166 				public:
167 					const off_t d_offset;
168 					const off_t d_length;
169 
170 					/**
171 					 * @brief Represents an on-screen area of the region.
172 					*/
173 					enum ScreenArea
174 					{
175 						SA_NONE    = 0,  /**< No/Unknown area. */
176 						SA_HEX     = 1,  /**< The hex (data) view. */
177 						SA_ASCII   = 2,  /**< The ASCII (text) view. */
178 						SA_SPECIAL = 4,  /**< Region-specific data area. */
179 					};
180 
181 					/**
182 					 * @brief Returns the offset of the byte at the given co-ordinates, negative if there isn't one.
183 					*/
184 					virtual std::pair<off_t, ScreenArea> offset_at_xy(DocumentCtrl &doc, int mouse_x_px, int64_t mouse_y_lines) = 0;
185 
186 					/**
187 					 * @brief Returns the offset of the byte nearest the given co-ordinates and the screen area.
188 					 *
189 					 * If type_hint is specified, and supported by the region
190 					 * type, the nearest character in that area will be
191 					 * returned rather than in the area under or closest to the
192 					*/
193 					virtual std::pair<off_t, ScreenArea> offset_near_xy(DocumentCtrl &doc, int mouse_x_px, int64_t mouse_y_lines, ScreenArea type_hint) = 0;
194 
195 					static const off_t CURSOR_PREV_REGION = -2;
196 					static const off_t CURSOR_NEXT_REGION = -3;
197 
198 					/**
199 					 * @brief Returns the offset of the cursor position left of the given offset. May return CURSOR_PREV_REGION.
200 					*/
201 					virtual off_t cursor_left_from(off_t pos) = 0;
202 
203 					/**
204 					 * @brief Returns the offset of the cursor position right of the given offset. May return CURSOR_NEXT_REGION.
205 					*/
206 					virtual off_t cursor_right_from(off_t pos) = 0;
207 
208 					/**
209 					 * @brief Returns the offset of the cursor position up from the given offset. May return CURSOR_PREV_REGION.
210 					*/
211 					virtual off_t cursor_up_from(off_t pos) = 0;
212 
213 					/**
214 					 * @brief Returns the offset of the cursor position down from the given offset. May return CURSOR_NEXT_REGION.
215 					*/
216 					virtual off_t cursor_down_from(off_t pos) = 0;
217 
218 					/**
219 					 * @brief Returns the offset of the cursor position at the start of the line from the given offset.
220 					*/
221 					virtual off_t cursor_home_from(off_t pos) = 0;
222 
223 					/**
224 					 * @brief Returns the offset of the cursor position at the end of the line from the given offset.
225 					*/
226 					virtual off_t cursor_end_from(off_t pos) = 0;
227 
228 					/**
229 					 * @brief Returns the screen column index of the given offset within the region.
230 					*/
231 					virtual int cursor_column(off_t pos) = 0;
232 
233 					/**
234 					 * @brief Returns the offset of the cursor position nearest the given column on the first screen line of the region.
235 					*/
236 					virtual off_t first_row_nearest_column(int column) = 0;
237 
238 					/**
239 					 * @brief Returns the offset of the cursor position nearest the given column on the last screen line of the region.
240 					*/
241 					virtual off_t last_row_nearest_column(int column) = 0;
242 
243 					/**
244 					 * @brief Returns the offset of the cursor position nearest the given column on the given row within the region.
245 					*/
246 					virtual off_t nth_row_nearest_column(int64_t row, int column) = 0;
247 
248 					/**
249 					 * @brief Calculate the on-screen bounding box of a byte in the region.
250 					*/
251 					virtual Rect calc_offset_bounds(off_t offset, DocumentCtrl *doc_ctrl) = 0;
252 
253 					/**
254 					 * @brief Find which screen areas exist for the cursor to occupy at the given offset.
255 					 * @return SA_XXX constants bitwise OR'd together.
256 					*/
257 					virtual ScreenArea screen_areas_at_offset(off_t offset, DocumentCtrl *doc_ctrl) = 0;
258 
259 					/**
260 					 * @brief Process key presses while the cursor is in this region.
261 					 * @return true if the event was handled, false otherwise.
262 					 *
263 					 * This method is called to process keypresses while the
264 					 * cursor is in this region.
265 					 *
266 					 * If it returns true, no further processing of the event
267 					 * will be performed, if it returns false, processing will
268 					 * continue and any default processing of the key press
269 					 * will be used.
270 					 *
271 					 * The method may be called multiple times for the same
272 					 * event if it returns false, the method MUST be idempotent
273 					 * when it returns false.
274 					*/
275 					virtual bool OnChar(DocumentCtrl *doc_ctrl, wxKeyEvent &event);
276 
277 					/**
278 					 * @brief Process a clipboard copy operation within this region.
279 					 * @return wxDataObject pointer, or NULL.
280 					 *
281 					 * This method is called to process copy events when the
282 					 * selection is entirely within a single region.
283 					 *
284 					 * Returns a pointer to a wxDataObject object to be placed
285 					 * into the clipboard, or NULL if the region has no special
286 					 * clipboard handling, in which case the default copy
287 					 * behaviour will take over.
288 					 *
289 					 * The caller is responsible for ensuring any returned
290 					 * wxDataObject is deleted.
291 					*/
292 					virtual wxDataObject *OnCopy(DocumentCtrl &doc_ctrl);
293 
294 					/**
295 					 * @brief Process a clipboard paste operation within this region.
296 					 * @return true if the event was handled, false otherwise.
297 					 *
298 					 * This method is called when the user attempts to paste and
299 					 * one or both of the following is true:
300 					 *
301 					 * a) A range of bytes exclusively within this region are selected.
302 					 *
303 					 * b) The cursor is within this region.
304 					 *
305 					 * The clipboard will already be locked by the caller when
306 					 * this method is called.
307 					 *
308 					 * If this method returns false, default paste handling
309 					 * will be invoked.
310 					*/
311 					virtual bool OnPaste(DocumentCtrl *doc_ctrl);
312 			};
313 
314 			class DataRegion: public GenericDataRegion
315 			{
316 				protected:
317 					off_t virt_offset;
318 
319 					int offset_text_x;  /* Virtual X coord of left edge of offsets. */
320 					int hex_text_x;     /* Virtual X coord of left edge of hex data. */
321 					int ascii_text_x;   /* Virtual X coord of left edge of ASCII data. */
322 
323 					unsigned int bytes_per_line_actual;  /* Number of bytes being displayed per line. */
324 					unsigned int first_line_pad_bytes;   /* Number of bytes to pad first line with. */
325 
326 				public:
327 					DataRegion(off_t d_offset, off_t d_length, off_t virt_offset);
328 
329 					int calc_width_for_bytes(DocumentCtrl &doc_ctrl, unsigned int line_bytes) const;
330 
331 				protected:
332 					virtual int calc_width(REHex::DocumentCtrl &doc) override;
333 					virtual void calc_height(REHex::DocumentCtrl &doc, wxDC &dc) override;
334 					virtual void draw(REHex::DocumentCtrl &doc, wxDC &dc, int x, int64_t y) override;
335 					virtual wxCursor cursor_for_point(REHex::DocumentCtrl &doc, int x, int64_t y_lines, int y_px) override;
336 
337 					off_t offset_at_xy_hex  (REHex::DocumentCtrl &doc, int mouse_x_px, uint64_t mouse_y_lines);
338 					off_t offset_at_xy_ascii(REHex::DocumentCtrl &doc, int mouse_x_px, uint64_t mouse_y_lines);
339 
340 					off_t offset_near_xy_hex  (REHex::DocumentCtrl &doc, int mouse_x_px, uint64_t mouse_y_lines);
341 					off_t offset_near_xy_ascii(REHex::DocumentCtrl &doc, int mouse_x_px, uint64_t mouse_y_lines);
342 
343 					virtual std::pair<off_t, ScreenArea> offset_at_xy(DocumentCtrl &doc, int mouse_x_px, int64_t mouse_y_lines) override;
344 					virtual std::pair<off_t, ScreenArea> offset_near_xy(DocumentCtrl &doc, int mouse_x_px, int64_t mouse_y_lines, ScreenArea type_hint) override;
345 
346 					virtual off_t cursor_left_from(off_t pos) override;
347 					virtual off_t cursor_right_from(off_t pos) override;
348 					virtual off_t cursor_up_from(off_t pos) override;
349 					virtual off_t cursor_down_from(off_t pos) override;
350 					virtual off_t cursor_home_from(off_t pos) override;
351 					virtual off_t cursor_end_from(off_t pos) override;
352 
353 					virtual int cursor_column(off_t pos) override;
354 					virtual off_t first_row_nearest_column(int column) override;
355 					virtual off_t last_row_nearest_column(int column) override;
356 					virtual off_t nth_row_nearest_column(int64_t row, int column) override;
357 
358 					virtual Rect calc_offset_bounds(off_t offset, DocumentCtrl *doc_ctrl) override;
359 					virtual ScreenArea screen_areas_at_offset(off_t offset, DocumentCtrl *doc_ctrl) override;
360 
361 					virtual Highlight highlight_at_off(off_t off) const;
362 
363 				friend DocumentCtrl;
364 			};
365 
366 			class DataRegionDocHighlight: public DataRegion
367 			{
368 				private:
369 					Document &doc;
370 
371 				public:
372 					DataRegionDocHighlight(off_t d_offset, off_t d_length, off_t virt_offset, Document &doc);
373 
374 				protected:
375 					virtual Highlight highlight_at_off(off_t off) const override;
376 			};
377 
378 			class CommentRegion: public Region
379 			{
380 				public:
381 
382 				off_t c_offset, c_length;
383 				const wxString &c_text;
384 
385 				bool truncate;
386 
387 				virtual void calc_height(REHex::DocumentCtrl &doc, wxDC &dc) override;
388 				virtual void draw(REHex::DocumentCtrl &doc, wxDC &dc, int x, int64_t y) override;
389 				virtual wxCursor cursor_for_point(REHex::DocumentCtrl &doc, int x, int64_t y_lines, int y_px) override;
390 
391 				CommentRegion(off_t c_offset, off_t c_length, const wxString &c_text, bool truncate, off_t indent_offset, off_t indent_length);
392 
393 				friend DocumentCtrl;
394 			};
395 
396 			DocumentCtrl(wxWindow *parent, SharedDocumentPointer &doc);
397 			~DocumentCtrl();
398 
399 			static const int BYTES_PER_LINE_FIT_BYTES  = 0;
400 			static const int BYTES_PER_LINE_FIT_GROUPS = -1;
401 			static const int BYTES_PER_LINE_MIN        = 1;
402 			static const int BYTES_PER_LINE_MAX        = 128;
403 
404 			int get_bytes_per_line();
405 			void set_bytes_per_line(int bytes_per_line);
406 
407 			unsigned int get_bytes_per_group();
408 			void set_bytes_per_group(unsigned int bytes_per_group);
409 
410 			bool get_show_offsets();
411 			void set_show_offsets(bool show_offsets);
412 
413 			OffsetBase get_offset_display_base() const;
414 			void set_offset_display_base(OffsetBase offset_display_base);
415 
416 			bool get_show_ascii();
417 			void set_show_ascii(bool show_ascii);
418 
419 			bool get_highlight_selection_match();
420 			void set_highlight_selection_match(bool highlight_selection_match);
421 
422 			off_t get_cursor_position() const;
423 			Document::CursorState get_cursor_state() const;
424 
425 			bool hex_view_active() const;
426 			bool ascii_view_active() const;
427 			bool special_view_active() const;
428 
429 			void set_cursor_position(off_t position, Document::CursorState cursor_state = Document::CSTATE_GOTO);
430 
431 			bool has_prev_cursor_position() const;
432 			void goto_prev_cursor_position();
433 
434 			bool has_next_cursor_position() const;
435 			void goto_next_cursor_position();
436 
437 			bool get_insert_mode();
438 			void set_insert_mode(bool enabled);
439 
440 			void linked_scroll_insert_self_after(DocumentCtrl *p);
441 			void linked_scroll_remove_self();
442 
443 			/**
444 			 * @brief Set the selection range.
445 			 *
446 			 * @param begin Data offset at beginning of selection.
447 			 * @param end Data offset at end of selection (inclusive).
448 			*/
449 			bool set_selection_raw(off_t begin, off_t end);
450 
451 			/**
452 			 * @brief Clear the selection (if any).
453 			*/
454 			void clear_selection();
455 
456 			/**
457 			 * @brief Returns true if there is a selection.
458 			*/
459 			bool has_selection();
460 
461 			/**
462 			 * @brief Returns the "raw" selection as a begin and end offset.
463 			 *
464 			 * NOTE: Unlike most "end" pointers, the end offset returned from this
465 			 * method is the last byte in the selection, not one past it.
466 			*/
467 			std::pair<off_t, off_t> get_selection_raw();
468 
469 			/**
470 			 * @brief Returns the subset of the current selection scoped to a region.
471 			 *
472 			 * The return value from this method is the offset (file relative) and the
473 			 * length of the current selection, scoped to the given region.
474 			 *
475 			 * If there is no selection, or the selection doesn't include any bytes
476 			 * from the given region, the returned length will be <= 0.
477 			*/
478 			std::pair<off_t, off_t> get_selection_in_region(GenericDataRegion *region);
479 
480 			/**
481 			 * @brief Returns the set of all bytes currently selected.
482 			 *
483 			 * NOTE: This method may be expensive to call, as it potentially has to
484 			 * iterate through all (data) regions in the file.
485 			*/
486 			OrderedByteRangeSet get_selection_ranges();
487 
488 			/**
489 			 * @brief Returns the offset and length of the selection, if linear.
490 			 *
491 			 * If there is no selection, or the selection isn't linear and contiguous, the length
492 			 * will be zero.
493 			*/
494 			std::pair<off_t, off_t> get_selection_linear();
495 
496 			const std::vector<Region*> &get_regions() const;
497 			const std::vector<GenericDataRegion*> &get_data_regions() const;
498 			void replace_all_regions(std::vector<Region*> &new_regions);
499 			bool region_OnChar(wxKeyEvent &event);
500 			GenericDataRegion *data_region_by_offset(off_t offset);
501 			std::vector<Region*>::iterator region_by_y_offset(int64_t y_offset);
502 
503 			/**
504 			 * @brief Compare two offsets in the address space defined by the regions.
505 			 *
506 			 * Returns zero if the two offsets are equal, a negative integer if a is
507 			 * less than b and a positive integer if a is greater than b.
508 			 *
509 			 * Throws an exception of type std::invalid_argument if either of the
510 			 * offsets are invalid.
511 			*/
512 			int region_offset_cmp(off_t a, off_t b);
513 
514 			/**
515 			 * @brief Increment an offset in the address space defined by the regions.
516 			 *
517 			 * @param base Base offset to start at.
518 			 * @param add Number of bytes to increment base by.
519 			 *
520 			 * @return New offset, negative if invalid.
521 			*/
522 			off_t region_offset_add(off_t base, off_t add);
523 
524 			/**
525 			 * @brief Decrement an offset in the address space defined by the regions.
526 			 *
527 			 * @param base Base offset to start at.
528 			 * @param add Number of bytes to decrement base by.
529 			 *
530 			 * @return New offset, negative if invalid.
531 			*/
532 			off_t region_offset_sub(off_t base, off_t sub);
533 
534 			/**
535 			 * @brief Check if a range of offsets is linear and contiguous.
536 			*/
537 			bool region_range_linear(off_t begin_offset, off_t end_offset_incl);
538 
539 			wxFont &get_font();
540 
541 			/**
542 			 * @brief Returns the current vertical scroll position, in lines.
543 			*/
544 			int64_t get_scroll_yoff() const;
545 
546 			/**
547 			 * @brief Set the vertical scroll position, in lines.
548 			*/
549 			void set_scroll_yoff(int64_t scroll_yoff, bool update_linked_scroll_others = true);
550 
551 			void OnPaint(wxPaintEvent &event);
552 			void OnErase(wxEraseEvent& event);
553 			void OnSize(wxSizeEvent &event);
554 			void OnScroll(wxScrollWinEvent &event);
555 			void OnWheel(wxMouseEvent &event);
556 			void OnChar(wxKeyEvent &event);
557 			void OnLeftDown(wxMouseEvent &event);
558 			void OnLeftUp(wxMouseEvent &event);
559 			void OnRightDown(wxMouseEvent &event);
560 			void OnMotion(wxMouseEvent &event);
561 			void OnSelectTick(wxTimerEvent &event);
562 			void OnMotionTick(int mouse_x, int mouse_y);
563 			void OnRedrawCursor(wxTimerEvent &event);
564 			void OnClearHighlight(wxCommandEvent &event);
565 			void OnIdle(wxIdleEvent &event);
566 			void OnFontSizeAdjustmentChanged(FontSizeAdjustmentEvent &event);
567 
568 		#ifndef UNIT_TEST
569 		private:
570 		#endif
571 			friend DataRegion;
572 			friend CommentRegion;
573 
574 			SharedDocumentPointer doc;
575 
576 			std::vector<Region*> regions;                  /**< List of regions to be displayed. */
577 			std::vector<GenericDataRegion*> data_regions;  /**< Subset of regions which are a GenericDataRegion. */
578 			std::vector<Region*> processing_regions;       /**< Subset of regions which are doing background processing. */
579 
580 			/** List of iterators into data_regions, sorted by d_offset. */
581 			std::vector< std::vector<GenericDataRegion*>::iterator > data_regions_sorted;
582 
583 			/* Fixed-width font used for drawing hex data. */
584 			wxFont hex_font;
585 
586 			/* Size of a character in hex_font. */
587 			unsigned char hf_height;
588 
589 			/* Size of the client area in pixels. */
590 			int client_width;
591 			int client_height;
592 
593 			/* Height of client area in lines. */
594 			unsigned int visible_lines;
595 
596 			/* Width of the scrollable area. */
597 			int virtual_width;
598 
599 			/* Display options */
600 			int bytes_per_line;
601 			unsigned int bytes_per_group;
602 
603 			bool offset_column{true};
604 			int offset_column_width;
605 			OffsetBase offset_display_base;
606 
607 			bool show_ascii;
608 
609 			bool highlight_selection_match;
610 
611 			int     scroll_xoff;
612 			int64_t scroll_yoff;
613 			int64_t scroll_yoff_max;
614 			int64_t scroll_ydiv;
615 
616 			DocumentCtrl *linked_scroll_prev;
617 			DocumentCtrl *linked_scroll_next;
618 
619 			int wheel_vert_accum;
620 			int wheel_horiz_accum;
621 
622 			off_t cpos_off{0};
623 			bool insert_mode{false};
624 
625 			static const size_t CPOS_HISTORY_LIMIT = 128;
626 
627 			std::vector<off_t> cpos_prev;
628 			std::vector<off_t> cpos_next;
629 
630 			off_t selection_begin;
631 			off_t selection_end;
632 
633 			bool cursor_visible;
634 			wxTimer redraw_cursor_timer;
635 
636 			static const int MOUSE_SELECT_INTERVAL = 100;
637 
638 			GenericDataRegion::ScreenArea mouse_down_area;
639 			off_t mouse_down_at_offset;
640 			int mouse_down_at_x;
641 			wxTimer mouse_select_timer;
642 			off_t mouse_shift_initial;
643 
644 			Document::CursorState cursor_state;
645 
646 			void _set_cursor_position(off_t position, Document::CursorState cursor_state, bool preserve_cpos_hist = false);
647 
648 			std::vector<GenericDataRegion*>::iterator _data_region_by_offset(off_t offset);
649 
650 			std::list<Region*>::iterator _region_by_y_offset(int64_t y_offset);
651 
652 			void _make_line_visible(int64_t line);
653 			void _make_x_visible(int x_px, int width_px);
654 
655 			void _make_byte_visible(off_t offset);
656 
657 			void _handle_width_change();
658 			void _handle_height_change();
659 			void _update_vscroll();
660 			void _update_vscroll_pos(bool update_linked_scroll_others = true);
661 
662 			/**
663 			 * @brief Fuzzy description of the DocumentCtrl scroll position.
664 			 *
665 			 * This struct describes the DocumentCtrl scroll position, in terms of its
666 			 * contents so it can be restored (as close as possible) when the window
667 			 * size changes or regions are added/removed/grow/shrink/etc.
668 			*/
669 			struct FuzzyScrollPosition
670 			{
671 				bool data_offset_valid;   /**< True if data_offset and data_offset_line are valid. */
672 				off_t data_offset;        /**< File offset used as reference point. */
673 				int64_t data_offset_line; /**< Visible (on-screen) line where data_offset is. */
674 
675 				bool region_idx_valid;    /**< True if region_idx and region_idx_line are valid. */
676 				size_t region_idx;        /**< Index of region whose first line is our reference point. */
677 				int64_t region_idx_line;  /**< Visible (on-screen) line where region begins (may be negative). */
678 
FuzzyScrollPositionREHex::DocumentCtrl::FuzzyScrollPosition679 				FuzzyScrollPosition():
680 					data_offset_valid(false),
681 					region_idx_valid(false) {}
682 			};
683 
684 			/**
685 			 * @brief Set scroll_yoff, clamped to valid range.
686 			*/
687 			void set_scroll_yoff_clamped(int64_t scroll_yoff);
688 
689 			/**
690 			 * @brief Fetch the current scroll position.
691 			*/
692 			FuzzyScrollPosition get_scroll_position_fuzzy();
693 
694 			/**
695 			 * @brief Jump to a fuzzy scroll position.
696 			*/
697 			void set_scroll_position_fuzzy(const FuzzyScrollPosition &fsp);
698 
699 			FuzzyScrollPosition saved_scroll_position;
700 
701 			/**
702 			 * @brief Save the current scroll position.
703 			*/
704 			void save_scroll_position();
705 
706 			/**
707 			 * @brief Restore the last saved scroll position.
708 			*/
709 			void restore_scroll_position();
710 
711 			void linked_scroll_visit_others(const std::function<void(DocumentCtrl*)> &func);
712 
713 			static const int PRECOMP_HF_STRING_WIDTH_TO = 512;
714 			unsigned int hf_string_width_precomp[PRECOMP_HF_STRING_WIDTH_TO];
715 
716 			static const size_t GETTEXTEXTENT_CACHE_SIZE = 4096;
717 			LRUCache<std::string, wxSize> hf_gte_cache;
718 
719 		public:
720 			static std::list<wxString> format_text(const wxString &text, unsigned int cols, unsigned int from_line = 0, unsigned int max_lines = -1);
721 			int indent_width(int depth);
722 			int get_offset_column_width();
723 			int get_virtual_width();
724 			bool get_cursor_visible();
725 
726 			int hf_char_width();
727 			int hf_char_height();
728 			int hf_string_width(int length);
729 			int hf_char_at_x(int x_px);
730 
731 			/* Stays at the bottom because it changes the protection... */
732 			DECLARE_EVENT_TABLE()
733 	};
734 }
735 
736 #endif /* !REHEX_DOCUMENTCTRL_HPP */
737