OSDN Git Service

c1ea590d45d77636f0318ea62412530b8b40dc58
[timidity41/timidity41.git] / interface / w32g_new_console.cpp
1 // TiMidity++ Win32 GUI New Console
2 // Copyright (c) 2018 Starg <https://osdn.net/projects/timidity41>
3
4
5 extern "C"
6 {
7
8 #ifdef HAVE_CONFIG_H
9 #include "config.h"
10 #endif /* HAVE_CONFIG_H */
11
12 #include "timidity.h"
13 #include "common.h"
14 #include "controls.h"
15 #include "instrum.h"
16 #include "playmidi.h"
17
18 #include "w32g.h"
19 #include "w32g_res.h"
20
21 #include "w32g_new_console.h"
22 }
23
24 #include <windows.h>
25 #include <windowsx.h>
26
27 #include <cstddef>
28 #include <cstdarg>
29 #include <cstdio>
30
31 #include <algorithm>
32 #include <array>
33 #include <numeric>
34 #include <optional>
35 #include <string>
36 #include <string_view>
37 #include <utility>
38 #include <vector>
39
40 #include <tchar.h>
41
42 #ifdef min
43 #undef min
44 #endif
45
46 #ifdef max
47 #undef max
48 #endif
49
50 namespace TimW32gNewConsole
51 {
52
53 LPCTSTR pClassName = _T("TimW32gNewConsole");
54
55 // Campbell color theme
56 // https://github.com/Microsoft/console/blob/master/tools/ColorTool/schemes/campbell.ini
57 const COLORREF BackgroundColor = RGB(12, 12, 12);
58 const COLORREF NormalColor = RGB(204, 204, 204);
59 const COLORREF ErrorColor = RGB(231, 72, 86);
60 const COLORREF WarningColor = RGB(249, 241, 165);
61 const COLORREF InfoColor = RGB(58, 150, 221);
62
63 using TString = std::basic_string<TCHAR>;
64 using TStringView = std::basic_string_view<TCHAR>;
65
66 bool CopyTextToClipboard(TStringView text)
67 {
68     bool ret = false;
69
70     if (::OpenClipboard(nullptr))
71     {
72         HGLOBAL hGlobal = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, (text.size() + 1) * sizeof(TCHAR));
73
74         if (hGlobal)
75         {
76             auto p = reinterpret_cast<LPTSTR>(::GlobalLock(hGlobal));
77             text.copy(p, text.size());
78             p[text.size()] = _T('\0');
79             ::GlobalUnlock(hGlobal);
80
81             ::EmptyClipboard();
82
83 #ifdef UNICODE
84             UINT format = CF_UNICODETEXT;
85 #else
86             UINT format = CF_TEXT;
87 #endif
88
89             if (::SetClipboardData(format, hGlobal))
90             {
91                 ret = true;
92             }
93             else
94             {
95                 ::GlobalFree(hGlobal);
96             }
97         }
98
99         ::CloseClipboard();
100     }
101
102     return ret;
103 }
104
105 template<typename T>
106 class UniqueLock
107 {
108 public:
109     UniqueLock() : m_pLock(nullptr)
110     {
111     }
112
113     explicit UniqueLock(T& lock) : m_pLock(&lock)
114     {
115         m_pLock->DoLockUnique();
116     }
117
118     UniqueLock(const UniqueLock&) = delete;
119     UniqueLock& operator=(const UniqueLock&) = delete;
120
121     UniqueLock(UniqueLock&& rhs) noexcept : m_pLock()
122     {
123         swap(rhs);
124     }
125
126     UniqueLock& operator=(UniqueLock&& rhs) noexcept
127     {
128         UniqueLock(std::move(rhs)).swap(*this);
129         return *this;
130     }
131
132     ~UniqueLock()
133     {
134         Unlock();
135     }
136
137     void swap(UniqueLock& rhs) noexcept
138     {
139         using std::swap;
140         swap(m_pLock, rhs.m_pLock);
141     }
142
143     void Unlock()
144     {
145         if (m_pLock)
146         {
147             m_pLock->DoUnlockUnique();
148             m_pLock = nullptr;
149         }
150     }
151
152 private:
153     T* m_pLock;
154 };
155
156 template<typename T>
157 class SharedLock
158 {
159 public:
160     SharedLock() : m_pLock(nullptr)
161     {
162     }
163
164     explicit SharedLock(T& lock) : m_pLock(&lock)
165     {
166         m_pLock->DoLockShared();
167     }
168
169     SharedLock(const SharedLock&) = delete;
170     SharedLock& operator=(const SharedLock&) = delete;
171
172     SharedLock(SharedLock&& rhs) noexcept : m_pLock()
173     {
174         swap(rhs);
175     }
176
177     SharedLock& operator=(SharedLock&& rhs) noexcept
178     {
179         SharedLock(std::move(rhs)).swap(*this);
180         return *this;
181     }
182
183     ~SharedLock()
184     {
185         Unlock();
186     }
187
188     void swap(SharedLock& rhs) noexcept
189     {
190         using std::swap;
191         swap(m_pLock, rhs.m_pLock);
192     }
193
194     void Unlock()
195     {
196         if (m_pLock)
197         {
198             m_pLock->DoUnlockShared();
199             m_pLock = nullptr;
200         }
201     }
202
203 private:
204     T* m_pLock;
205 };
206
207 class SRWLock
208 {
209     friend class UniqueLock<SRWLock>;
210     friend class SharedLock<SRWLock>;
211
212 public:
213     SRWLock()
214     {
215         ::InitializeSRWLock(&m_Lock);
216     }
217
218     SRWLock(const SRWLock&) = delete;
219     SRWLock& operator=(const SRWLock&) = delete;
220     SRWLock(SRWLock&&) = delete;
221     SRWLock& operator=(SRWLock&&) = delete;
222
223     ~SRWLock() = default;
224
225     UniqueLock<SRWLock> LockUnique()
226     {
227         return UniqueLock<SRWLock>(*this);
228     }
229
230     SharedLock<SRWLock> LockShared()
231     {
232         return SharedLock<SRWLock>(*this);
233     }
234
235     SRWLOCK* Get()
236     {
237         return &m_Lock;
238     }
239
240 private:
241     void DoLockUnique()
242     {
243         ::AcquireSRWLockExclusive(&m_Lock);
244     }
245
246     void DoUnlockUnique()
247     {
248         ::ReleaseSRWLockExclusive(&m_Lock);
249     }
250
251     void DoLockShared()
252     {
253         ::AcquireSRWLockShared(&m_Lock);
254     }
255
256     void DoUnlockShared()
257     {
258         ::ReleaseSRWLockShared(&m_Lock);
259     }
260
261     SRWLOCK m_Lock;
262 };
263
264 struct TextLocationInfo
265 {
266     std::size_t Line;
267     std::size_t Column;
268 };
269
270 struct StyledLineFragment
271 {
272     std::size_t Offset; // offset in std::string
273     std::size_t Length;
274     COLORREF Color;
275 };
276
277 struct StyledLine
278 {
279     std::size_t Offset; // offset in std::vector<StyledLineFragment>
280     std::size_t Length;
281 };
282
283 class StyledTextBuffer
284 {
285 public:
286     StyledTextBuffer()
287     {
288     }
289
290     void Clear()
291     {
292         m_Fragments.clear();
293         m_Lines.clear();
294         m_String.clear();
295         m_MaxColumnLength = 0;
296     }
297
298     void Append(COLORREF color, LPCTSTR pText)
299     {
300         Append(color, TStringView(pText));
301     }
302
303     void Append(COLORREF color, TStringView text)
304     {
305         std::size_t offset = 0;
306
307         while (offset < text.size())
308         {
309             // split input into lines
310             std::size_t nlOffset = text.find_first_of(_T("\r\n"), offset);
311
312             if (nlOffset == text.npos)
313             {
314                 AppendNoNewline(color, text.substr(offset));
315                 break;
316             }
317             else
318             {
319                 if (offset < nlOffset)
320                 {
321                     AppendNoNewline(color, text.substr(offset, offset - nlOffset));
322                 }
323
324                 AppendNewline();
325
326                 if (text[nlOffset] == _T('\r') && nlOffset + 1 < text.size() && text[nlOffset + 1] == _T('\n'))
327                 {
328                     offset = nlOffset + 2;
329                 }
330                 else
331                 {
332                     offset = nlOffset + 1;
333                 }
334             }
335         }
336     }
337
338     void AppendNewline()
339     {
340         m_Lines.push_back({m_Fragments.size(), 0});
341     }
342
343     std::size_t GetLineCount() const
344     {
345         return m_Lines.size();
346     }
347
348     std::size_t GetLastLineNumber() const
349     {
350         std::size_t n = GetLineCount();
351         return n > 0 ? n - 1 : 0;
352     }
353
354     std::size_t GetMaxColumnLength() const
355     {
356         return m_MaxColumnLength;
357     }
358
359     std::size_t GetMaxLastColumnNumber() const
360     {
361         std::size_t n = GetMaxColumnLength();
362         return n > 0 ? n - 1 : 0;
363     }
364
365     std::size_t GetColumnLength(std::size_t line) const
366     {
367         return std::accumulate(
368             m_Fragments.begin() + m_Lines[line].Offset,
369             m_Fragments.begin() + m_Lines[line].Offset + m_Lines[line].Length,
370             0,
371             [] (auto&& a, auto&& b)
372             {
373                 return a + b.Length;
374             }
375         );
376     }
377
378     std::size_t GetLastColumnNumber(std::size_t line) const
379     {
380         std::size_t n = GetColumnLength(line);
381         return n > 0 ? n - 1 : 0;
382     }
383
384     TStringView GetString() const
385     {
386         return m_String;
387     }
388
389     const std::vector<StyledLine>& GetLines() const
390     {
391         return m_Lines;
392     }
393
394     const std::vector<StyledLineFragment>& GetFragments() const
395     {
396         return m_Fragments;
397     }
398
399     TStringView GetLineString(std::size_t line) const
400     {
401         const auto& lineInfo = m_Lines[line];
402
403         if (lineInfo.Length == 0)
404         {
405             return {};
406         }
407
408         std::size_t first = m_Fragments[lineInfo.Offset].Offset;
409         std::size_t last = lineInfo.Offset + lineInfo.Length == m_Fragments.size()
410             ? m_String.size()
411             : m_Fragments[lineInfo.Offset + lineInfo.Length].Offset;
412
413         return TStringView(m_String.data() + first, last - first);
414     }
415
416     TString CopySubstring(TextLocationInfo start, TextLocationInfo end) const
417     {
418         TString ret(GetLineString(start.Line).substr(start.Column, start.Line < end.Line ? TStringView::npos : end.Column + 1 - start.Column));
419
420         for (std::size_t line = start.Line + 1; line <= end.Line; line++)
421         {
422             ret.append(_T("\r\n"));
423             ret.append(GetLineString(line).substr(0, line < end.Line ? TStringView::npos : end.Column + 1));
424         }
425
426         return ret;
427     }
428
429 private:
430     void AppendNoNewline(COLORREF color, TStringView text)
431     {
432         std::size_t stringOffset = m_String.size();
433         m_String.append(text);
434
435         std::size_t fragmentOffset = m_Fragments.size();
436         m_Fragments.push_back({stringOffset, text.size(), color});
437
438         if (m_Lines.empty())
439         {
440             m_Lines.push_back({fragmentOffset, 1});
441         }
442         else
443         {
444             m_Lines.back().Length++;
445         }
446
447         // update m_MaxColumnLength
448         m_MaxColumnLength = std::max(GetColumnLength(GetLastLineNumber()), m_MaxColumnLength);
449     }
450
451     TString m_String;
452     std::vector<StyledLine> m_Lines;
453     std::vector<StyledLineFragment> m_Fragments;
454     std::size_t m_MaxColumnLength = 0;    // max number of characters in line
455 };
456
457 StyledTextBuffer GlobalNewConsoleBuffer;
458
459 class NewConsoleWindow
460 {
461     enum TimerKind
462     {
463         RedrawTimer,
464         DragScrollTimer
465     };
466
467 public:
468     explicit NewConsoleWindow(StyledTextBuffer& buffer) : m_Buffer(buffer)
469     {
470     }
471
472     NewConsoleWindow(const NewConsoleWindow&) = delete;
473     NewConsoleWindow& operator=(const NewConsoleWindow&) = delete;
474     ~NewConsoleWindow() = default;
475
476     void Clear()
477     {
478         auto lock = m_Lock.LockUnique();
479         m_Buffer.Clear();
480         m_SelStart.reset();
481         m_SelEnd.reset();
482         m_CurrentTopLineNumber = 0;
483         m_CurrentLeftColumnNumber = 0;
484     }
485
486     void Write(LPCTSTR pText)
487     {
488         Write(NormalColor, pText, false);
489     }
490
491     void WriteV(LPCTSTR pFormat, va_list args)
492     {
493         std::array<TCHAR, BUFSIZ> buf;
494         std::vsnprintf(buf.data(), buf.size(), pFormat, args);
495         Write(NormalColor, buf.data(), false);
496     }
497
498     void Write(COLORREF color, LPCTSTR pText, bool newline)
499     {
500         auto lock = m_Lock.LockUnique();
501         bool shouldAutoScroll = ShouldAutoScroll();
502
503         m_Buffer.Append(color, pText);
504
505         if (newline)
506         {
507             m_Buffer.AppendNewline();
508         }
509
510         if (shouldAutoScroll)
511         {
512             DoAutoScroll();
513         }
514     }
515
516     static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
517     {
518         auto pConsoleWindow = reinterpret_cast<NewConsoleWindow*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
519
520         switch (msg)
521         {
522         case WM_CREATE:
523             pConsoleWindow = new NewConsoleWindow(GlobalNewConsoleBuffer);
524             ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pConsoleWindow));
525             pConsoleWindow->m_hWnd = hWnd;
526             pConsoleWindow->OnCreate();
527             return 0;
528
529         case WM_DESTROY:
530             if (pConsoleWindow)
531             {
532                 pConsoleWindow->OnDestroy();
533                 pConsoleWindow->m_hWnd = nullptr;
534                 ::SetWindowLongPtr(hWnd, GWLP_USERDATA, 0);
535                 delete pConsoleWindow;
536             }
537             return 0;
538
539         default:
540             if (pConsoleWindow)
541             {
542                 switch (msg)
543                 {
544                 case WM_SIZE:
545                     pConsoleWindow->OnSize();
546                     return 0;
547
548                 case WM_PAINT:
549                     pConsoleWindow->OnPaint();
550                     return 0;
551
552                 case WM_VSCROLL:
553                     pConsoleWindow->OnVScroll(wParam, lParam);
554                     return 0;
555
556                 case WM_HSCROLL:
557                     pConsoleWindow->OnHScroll(wParam, lParam);
558                     return 0;
559
560                 case WM_MOUSEWHEEL:
561                     pConsoleWindow->OnMouseWheel(wParam, lParam);
562                     return 0;
563
564                 case WM_MOUSEHWHEEL:
565                     pConsoleWindow->OnMouseHWheel(wParam, lParam);
566                     return 0;
567
568                 case WM_LBUTTONDOWN:
569                     pConsoleWindow->OnLButtonDown(wParam, lParam);
570                     return 0;
571
572                 case WM_LBUTTONUP:
573                     pConsoleWindow->OnLButtonUp(wParam, lParam);
574                     return 0;
575
576                 case WM_MOUSEMOVE:
577                     pConsoleWindow->OnMouseMove(wParam, lParam);
578                     return 0;
579
580                 case WM_KEYDOWN:
581                     pConsoleWindow->OnKeyDown(wParam, lParam);
582                     return 0;
583
584                 case WM_TIMER:
585                     pConsoleWindow->OnTimer(wParam, lParam);
586                     return 0;
587
588                 default:
589                     break;
590                 }
591             }
592             break;
593         }
594
595         return ::DefWindowProc(hWnd, msg, wParam, lParam);
596     }
597
598 private:
599     void OnCreate()
600     {
601         ::SetTimer(m_hWnd, RedrawTimer, 200, nullptr);
602
603         ::ShowScrollBar(m_hWnd, SB_BOTH, true);
604         InitializeGDIResource();
605         InvalidateRect(m_hWnd, nullptr, true);
606     }
607
608     void OnDestroy()
609     {
610         ::KillTimer(m_hWnd, RedrawTimer);
611         UninitializeGDIResource();
612     }
613
614     void OnSize()
615     {
616         // Recreate everything
617         InitializeGDIResource();
618         InvalidateRect(m_hWnd, nullptr, true);
619     }
620
621     void OnPaint()
622     {
623         PAINTSTRUCT ps;
624         HDC hDC = ::BeginPaint(m_hWnd, &ps);
625
626         RECT rc;
627         ::GetClientRect(m_hWnd, &rc);
628         ::FillRect(m_hBackDC, &rc, m_hBgBrush);
629
630         {
631             auto lock = m_Lock.LockShared();
632             UpdateScrollBarsNoLock();
633
634             int lineCount = std::min(static_cast<int>(m_Buffer.GetLineCount() - m_CurrentTopLineNumber), GetVisibleLinesInWindow());
635
636             for (int i = 0; i < lineCount; i++)
637             {
638                 auto lineInfo = m_Buffer.GetLines()[m_CurrentTopLineNumber + i];
639                 auto first = m_Buffer.GetFragments().begin() + lineInfo.Offset;
640                 auto last = first + lineInfo.Length;
641
642                 int x = -m_CurrentLeftColumnNumber * m_FontWidth;
643                 int y = i * m_FontHeight;
644
645                 std::for_each(
646                     first,
647                     last,
648                     [str = m_Buffer.GetString(), hWnd = m_hWnd, hDC = m_hBackDC, &x, y, fontWidth = m_FontWidth] (const StyledLineFragment& lf)
649                     {
650                         ::SetTextColor(hDC, lf.Color);
651                         ::TextOut(hDC, x, y, str.data() + lf.Offset, lf.Length);
652                         x += lf.Length * fontWidth;
653                     }
654                 );
655             }
656
657             if (m_SelStart.has_value() && m_SelEnd.has_value())
658             {
659                 auto s = m_SelStart;
660                 auto e = m_SelEnd;
661
662                 if (std::make_pair(s->Line, s->Column) > std::make_pair(e->Line, e->Column))
663                 {
664                     s.swap(e);
665                 }
666
667                 auto [x, y] = PositionFromTextLocation(*s);
668                 auto [xe, ye] = PositionFromTextLocation(*e);
669
670                 if (s->Line == e->Line)
671                 {
672                     ::BitBlt(m_hBackDC, x, y, xe - x + m_FontWidth, m_FontHeight, nullptr, 0, 0, DSTINVERT);
673                 }
674                 else
675                 {
676                     ::BitBlt(m_hBackDC, x, y, (rc.right - rc.left) - x, m_FontHeight, nullptr, 0, 0, DSTINVERT);
677                     ::BitBlt(m_hBackDC, 0, y + m_FontHeight, rc.right - rc.left, ye - (y + m_FontHeight), nullptr, 0, 0, DSTINVERT);
678                     ::BitBlt(m_hBackDC, 0, ye, xe + m_FontWidth, m_FontHeight, nullptr, 0, 0, DSTINVERT);
679                 }
680             }
681         }
682
683         ::BitBlt(hDC, 0, 0, rc.right - rc.left, rc.bottom - rc.top, m_hBackDC, 0, 0, SRCCOPY);
684         ::EndPaint(m_hWnd, &ps);
685     }
686
687     void OnVScroll(WPARAM wParam, LPARAM)
688     {
689         auto lock = m_Lock.LockUnique();
690
691         switch (LOWORD(wParam))
692         {
693         case SB_TOP:
694             m_CurrentTopLineNumber = 0;
695             break;
696
697         case SB_BOTTOM:
698             m_CurrentTopLineNumber = GetMaxTopLineNumber();
699             break;
700
701         case SB_LINEUP:
702             m_CurrentTopLineNumber = std::max(0, m_CurrentTopLineNumber - 1);
703             break;
704
705         case SB_LINEDOWN:
706             m_CurrentTopLineNumber = std::min(m_CurrentTopLineNumber + 1, GetMaxTopLineNumber());
707             break;
708
709         case SB_PAGEUP:
710             m_CurrentTopLineNumber = std::max(0, m_CurrentTopLineNumber - GetVisibleLinesInWindow());
711             break;
712
713         case SB_PAGEDOWN:
714             m_CurrentTopLineNumber = std::min(m_CurrentTopLineNumber + GetVisibleLinesInWindow(), GetMaxTopLineNumber());
715             break;
716
717         case SB_THUMBPOSITION:
718         case SB_THUMBTRACK:
719             {
720                 SCROLLINFO si = {};
721                 si.cbSize = sizeof(SCROLLINFO);
722                 si.fMask = SIF_TRACKPOS;
723                 ::GetScrollInfo(m_hWnd, SB_VERT, &si);
724                 m_CurrentTopLineNumber = si.nTrackPos;
725             }
726             break;
727
728         default:
729             break;
730         }
731
732         InvalidateRect(m_hWnd, nullptr, true);
733     }
734
735     void OnHScroll(WPARAM wParam, LPARAM)
736     {
737         auto lock = m_Lock.LockUnique();
738
739         switch (LOWORD(wParam))
740         {
741         case SB_LEFT:
742             m_CurrentLeftColumnNumber = 0;
743             break;
744
745         case SB_RIGHT:
746             m_CurrentLeftColumnNumber = GetMaxLeftColumnNumber();
747             break;
748
749         case SB_LINELEFT:
750             m_CurrentLeftColumnNumber = std::max(0, m_CurrentLeftColumnNumber - 1);
751             break;
752
753         case SB_LINERIGHT:
754             m_CurrentLeftColumnNumber = std::min(m_CurrentLeftColumnNumber + 1, GetMaxLeftColumnNumber());
755             break;
756
757         case SB_PAGELEFT:
758             m_CurrentLeftColumnNumber = std::max(0, m_CurrentLeftColumnNumber - GetVisileColumnsInWindow());
759             break;
760
761         case SB_PAGERIGHT:
762             m_CurrentLeftColumnNumber = std::min(m_CurrentLeftColumnNumber + GetVisileColumnsInWindow(), GetMaxLeftColumnNumber());
763             break;
764
765         case SB_THUMBPOSITION:
766         case SB_THUMBTRACK:
767             {
768                 SCROLLINFO si = {};
769                 si.cbSize = sizeof(SCROLLINFO);
770                 si.fMask = SIF_TRACKPOS;
771                 ::GetScrollInfo(m_hWnd, SB_HORZ, &si);
772                 m_CurrentLeftColumnNumber = si.nTrackPos;
773             }
774             break;
775
776         default:
777             break;
778         }
779
780         InvalidateRect(m_hWnd, nullptr, true);
781     }
782
783     void OnMouseWheel(WPARAM wParam, LPARAM)
784     {
785         auto lock = m_Lock.LockUnique();
786
787         m_CurrentTopLineNumber = std::clamp(
788             m_CurrentTopLineNumber - GET_WHEEL_DELTA_WPARAM(wParam) * 3 / WHEEL_DELTA,
789             0,
790             GetMaxTopLineNumber()
791         );
792
793         InvalidateRect(m_hWnd, nullptr, true);
794     }
795
796     void OnMouseHWheel(WPARAM wParam, LPARAM)
797     {
798         auto lock = m_Lock.LockUnique();
799
800         m_CurrentLeftColumnNumber = std::clamp(
801             m_CurrentLeftColumnNumber + GET_WHEEL_DELTA_WPARAM(wParam) * 4 / WHEEL_DELTA,
802             0,
803             GetMaxLeftColumnNumber()
804         );
805
806         InvalidateRect(m_hWnd, nullptr, true);
807     }
808
809     void OnLButtonDown(WPARAM, LPARAM lParam)
810     {
811         auto lock = m_Lock.LockShared();
812         m_SelStart = TextLocationFromPosition(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), true);
813         m_SelEnd = m_SelStart;
814
815         if (m_SelStart.has_value())
816         {
817             SetCapture(m_hWnd);
818             ::SetTimer(m_hWnd, DragScrollTimer, 100, nullptr);
819         }
820
821         InvalidateRect(m_hWnd, nullptr, true);
822     }
823
824     void OnLButtonUp(WPARAM, LPARAM lParam)
825     {
826         if (m_SelStart.has_value())
827         {
828             ::KillTimer(m_hWnd, DragScrollTimer);
829             ::ReleaseCapture();
830
831             auto lock = m_Lock.LockShared();
832             m_SelEnd = TextLocationFromPosition(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), false);
833
834             if (m_SelStart.has_value() && m_SelEnd.has_value())
835             {
836                 if (std::make_pair(m_SelStart->Line, m_SelStart->Column) > std::make_pair(m_SelEnd->Line, m_SelEnd->Column))
837                 {
838                     m_SelStart.swap(m_SelEnd);
839                 }
840
841                 CopyTextToClipboard(m_Buffer.CopySubstring(*m_SelStart, *m_SelEnd));
842             }
843             else
844             {
845                 m_SelStart.reset();
846                 m_SelEnd.reset();
847             }
848
849             InvalidateRect(m_hWnd, nullptr, true);
850         }
851     }
852
853     void OnMouseMove(WPARAM wParam, LPARAM lParam)
854     {
855         if (m_SelStart.has_value() && (wParam & MK_LBUTTON))
856         {
857             auto lock = m_Lock.LockShared();
858             m_SelEnd = TextLocationFromPosition(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), false);
859             InvalidateRect(m_hWnd, nullptr, true);
860         }
861     }
862
863     void OnKeyDown(WPARAM wParam, LPARAM)
864     {
865         auto lock = m_Lock.LockUnique();
866
867         switch (wParam)
868         {
869         case 'A':
870             if (::GetKeyState(VK_CONTROL) < 0)
871             {
872                 m_SelStart = {0, 0};
873                 std::size_t lastLine = m_Buffer.GetLastLineNumber();
874                 m_SelEnd = {lastLine, m_Buffer.GetLastColumnNumber(lastLine)};
875                 CopyTextToClipboard(m_Buffer.CopySubstring(*m_SelStart, *m_SelEnd));
876             }
877             break;
878
879         case VK_UP:
880         case 'K':
881         case 'P':
882             m_CurrentTopLineNumber = std::max(0, m_CurrentTopLineNumber - 1);
883             break;
884
885         case VK_DOWN:
886         case 'J':
887         case 'N':
888             m_CurrentTopLineNumber = std::min(m_CurrentTopLineNumber + 1, GetMaxTopLineNumber());
889             break;
890
891         case VK_LEFT:
892         case 'H':
893         case 'B':
894             m_CurrentLeftColumnNumber = std::max(0, m_CurrentLeftColumnNumber - 1);
895             break;
896
897         case VK_RIGHT:
898         case 'L':
899         case 'F':
900             m_CurrentLeftColumnNumber = std::min(m_CurrentLeftColumnNumber + 1, GetMaxLeftColumnNumber());
901             break;
902
903         case VK_PRIOR:
904             m_CurrentTopLineNumber = std::max(0, m_CurrentTopLineNumber - GetVisibleLinesInWindow());
905             break;
906
907         case VK_NEXT:
908             m_CurrentTopLineNumber = std::min(m_CurrentTopLineNumber + GetVisibleLinesInWindow(), GetMaxTopLineNumber());
909             break;
910
911         case VK_HOME:
912             m_CurrentTopLineNumber = 0;
913             break;
914
915         case VK_END:
916             m_CurrentTopLineNumber = GetMaxTopLineNumber();
917             break;
918
919         default:
920             break;
921         }
922
923         InvalidateRect(m_hWnd, nullptr, true);
924     }
925
926     void OnTimer(WPARAM wParam, LPARAM)
927     {
928         switch (wParam)
929         {
930         case RedrawTimer:
931             InvalidateRect(m_hWnd, nullptr, true);
932             break;
933
934         case DragScrollTimer:
935             {
936                 POINT pt;
937                 ::GetCursorPos(&pt);
938                 ::ScreenToClient(m_hWnd, &pt);
939
940                 auto lock = m_Lock.LockUnique();
941                 DoDragScrollNoLock(pt.x, pt.y);
942
943                 if (m_SelStart.has_value())
944                 {
945                     m_SelEnd = TextLocationFromPosition(pt.x, pt.y, false);
946                     InvalidateRect(m_hWnd, nullptr, true);
947                 }
948             }
949             break;
950
951         default:
952             break;
953         }
954     }
955
956     void InitializeGDIResource()
957     {
958         UninitializeGDIResource();
959
960         HDC hDC = ::GetDC(m_hWnd);
961         m_hBackDC = ::CreateCompatibleDC(hDC);
962         m_SavedDCState = ::SaveDC(m_hBackDC);
963
964         RECT rc;
965         ::GetClientRect(m_hWnd, &rc);
966         m_hBackBitmap = ::CreateCompatibleBitmap(hDC, rc.right - rc.left, rc.bottom - rc.top);
967         ::SelectObject(m_hBackDC, m_hBackBitmap);
968
969         ::ReleaseDC(m_hWnd, hDC);
970
971 #ifdef JAPANESE
972         m_hFont = ::CreateFont(
973             -14,
974             0,
975             0,
976             0,
977             FW_REGULAR,
978             false,
979             false,
980             false,
981             SHIFTJIS_CHARSET,
982             OUT_DEFAULT_PRECIS,
983             CLIP_DEFAULT_PRECIS,
984             DEFAULT_QUALITY,
985             FIXED_PITCH | FF_DONTCARE,
986             _T("\82l\82\83S\83V\83b\83N")
987         );
988 #else
989         m_hFont = ::CreateFont(
990             -14,
991             0,
992             0,
993             0,
994             FW_REGULAR,
995             false,
996             false,
997             false,
998             DEFAULT_CHARSET,
999             OUT_DEFAULT_PRECIS,
1000             CLIP_DEFAULT_PRECIS,
1001             DEFAULT_QUALITY,
1002             FIXED_PITCH | FF_DONTCARE,
1003             _T("Consolas")
1004         );
1005 #endif
1006         ::SelectObject(m_hBackDC, m_hFont);
1007         ::SetBkColor(m_hBackDC, BackgroundColor);
1008
1009         TEXTMETRIC tm;
1010         ::GetTextMetrics(m_hBackDC, &tm);
1011         m_FontHeight = tm.tmHeight;
1012         m_FontWidth = tm.tmAveCharWidth;
1013
1014         m_hBgBrush = ::CreateSolidBrush(BackgroundColor);
1015     }
1016
1017     void UninitializeGDIResource()
1018     {
1019         if (m_hBgBrush)
1020         {
1021             ::DeleteObject(m_hBgBrush);
1022             m_hBgBrush = nullptr;
1023         }
1024
1025         if (m_hBackDC)
1026         {
1027             ::RestoreDC(m_hBackDC, m_SavedDCState);
1028             m_SavedDCState = 0;
1029             ::DeleteObject(m_hFont);
1030             m_hFont = nullptr;
1031             ::DeleteObject(m_hBackBitmap);
1032             m_hBackBitmap = nullptr;
1033             ::DeleteDC(m_hBackDC);
1034             m_hBackDC = nullptr;
1035         }
1036     }
1037
1038     int GetMaxTopLineNumber() const
1039     {
1040         return std::max(0, static_cast<int>(m_Buffer.GetLineCount() - GetVisibleLinesInWindow()));
1041     }
1042
1043     int GetMaxLeftColumnNumber() const
1044     {
1045         return std::max(0, static_cast<int>(m_Buffer.GetMaxColumnLength() - GetVisileColumnsInWindow()));
1046     }
1047
1048     int GetVisibleLinesInWindow() const
1049     {
1050         RECT rc;
1051         ::GetClientRect(m_hWnd, &rc);
1052         return (rc.bottom - rc.top) / m_FontHeight;
1053     }
1054
1055     int GetVisileColumnsInWindow() const
1056     {
1057         RECT rc;
1058         ::GetClientRect(m_hWnd, &rc);
1059         return (rc.right - rc.left) / m_FontWidth;
1060     }
1061
1062     void DoDragScrollNoLock(int x, int y)
1063     {
1064         RECT rc;
1065         ::GetClientRect(m_hWnd, &rc);
1066
1067         if (x < rc.left)
1068         {
1069             m_CurrentLeftColumnNumber = std::max(0, m_CurrentLeftColumnNumber - 1);
1070         }
1071         else if (rc.right <= x)
1072         {
1073             m_CurrentLeftColumnNumber = std::min(m_CurrentLeftColumnNumber + 1, GetMaxLeftColumnNumber());
1074         }
1075
1076         if (y < rc.top)
1077         {
1078             m_CurrentTopLineNumber = std::max(0, m_CurrentTopLineNumber - 1);
1079         }
1080         else if (rc.bottom <= y)
1081         {
1082             m_CurrentTopLineNumber = std::min(m_CurrentTopLineNumber + 1, GetMaxTopLineNumber());
1083         }
1084     }
1085
1086     bool ShouldAutoScroll() const
1087     {
1088         return !m_SelStart.has_value() && !m_SelEnd.has_value() && GetMaxTopLineNumber() <= m_CurrentTopLineNumber;
1089     }
1090
1091     void DoAutoScroll()
1092     {
1093         m_CurrentTopLineNumber = GetMaxTopLineNumber();
1094     }
1095
1096     void UpdateScrollBarsNoLock()
1097     {
1098         SCROLLINFO siv = {};
1099         SCROLLINFO sih = {};
1100
1101         siv.cbSize = sizeof(SCROLLINFO);
1102         siv.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
1103         siv.nMin = 0;
1104         siv.nMax = m_Buffer.GetLastLineNumber();
1105         siv.nPage = static_cast<UINT>(GetVisibleLinesInWindow());
1106         siv.nPos = m_CurrentTopLineNumber;
1107
1108         sih.cbSize = sizeof(SCROLLINFO);
1109         sih.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
1110         sih.nMin = 0;
1111         sih.nMax = m_Buffer.GetMaxLastColumnNumber();
1112         sih.nPage = static_cast<UINT>(GetVisileColumnsInWindow());
1113         sih.nPos = m_CurrentLeftColumnNumber;
1114
1115         ::SetScrollInfo(m_hWnd, SB_VERT, &siv, true);
1116         ::SetScrollInfo(m_hWnd, SB_HORZ, &sih, true);
1117     }
1118
1119     std::optional<TextLocationInfo> TextLocationFromPosition(int x, int y, bool exact) const
1120     {
1121         int line = m_CurrentTopLineNumber + y / m_FontHeight;
1122
1123         if (!exact)
1124         {
1125             if (line < 0 || m_Buffer.GetLineCount() == 0)
1126             {
1127                 return TextLocationInfo{static_cast<std::size_t>(0), static_cast<std::size_t>(0)};
1128             }
1129             else if (m_Buffer.GetLineCount() <= line)
1130             {
1131                 std::size_t lastLine = m_Buffer.GetLastLineNumber();
1132                 return TextLocationInfo{lastLine, m_Buffer.GetLastColumnNumber(lastLine)};
1133             }
1134         }
1135
1136         if (0 <= line && line < m_Buffer.GetLineCount())
1137         {
1138             int col = m_CurrentLeftColumnNumber + x / m_FontWidth;
1139
1140             if (!exact)
1141             {
1142                 return TextLocationInfo{
1143                     static_cast<std::size_t>(line),
1144                     static_cast<std::size_t>(std::clamp(col, 0, static_cast<int>(m_Buffer.GetLastColumnNumber(line))))
1145                 };
1146             }
1147
1148             if (0 <= col && col < m_Buffer.GetColumnLength(line))
1149             {
1150                 return TextLocationInfo{static_cast<std::size_t>(line), static_cast<std::size_t>(col)};
1151             }
1152         }
1153
1154         return std::nullopt;
1155     }
1156
1157     std::pair<int, int> PositionFromTextLocation(TextLocationInfo loc) const
1158     {
1159         return {
1160             static_cast<int>((loc.Column - m_CurrentLeftColumnNumber) * m_FontWidth),
1161             static_cast<int>((loc.Line - m_CurrentTopLineNumber) * m_FontHeight)
1162         };
1163     }
1164
1165     StyledTextBuffer& m_Buffer;
1166     std::optional<TextLocationInfo> m_SelStart;
1167     std::optional<TextLocationInfo> m_SelEnd;   // inclusive
1168     SRWLock m_Lock;
1169
1170     HWND m_hWnd = nullptr;
1171
1172     // scroll info
1173     int m_CurrentTopLineNumber = 0;    // line # of the top line of the window
1174     int m_CurrentLeftColumnNumber = 0;
1175
1176     HDC m_hBackDC = nullptr;
1177     int m_SavedDCState = 0;
1178     HBITMAP m_hBackBitmap = nullptr;
1179     HFONT m_hFont = nullptr;
1180     int m_FontHeight = 0;
1181     int m_FontWidth = 0;   // monospace font only
1182     HBRUSH m_hBgBrush = nullptr;
1183 };
1184
1185 } // namespace TimW32gNewConsole
1186
1187 extern "C" void ClearNewConsoleBuffer(void)
1188 {
1189     if (::IsWindow(hConsoleWnd))
1190     {
1191         auto pConsoleWindow = reinterpret_cast<TimW32gNewConsole::NewConsoleWindow*>(
1192             ::GetWindowLongPtr(::GetDlgItem(hConsoleWnd, IDC_EDIT), GWLP_USERDATA)
1193         );
1194
1195         if (pConsoleWindow)
1196         {
1197             pConsoleWindow->Clear();
1198             return;
1199         }
1200     }
1201
1202     TimW32gNewConsole::GlobalNewConsoleBuffer.Clear();
1203 }
1204
1205 extern "C" void NewConsoleBufferWriteCMsg(int type, int verbosity_level, LPCTSTR str)
1206 {
1207     COLORREF color = TimW32gNewConsole::NormalColor;
1208
1209     if (type == CMSG_FATAL || type == CMSG_ERROR)
1210     {
1211         color = TimW32gNewConsole::ErrorColor;
1212     }
1213     else if (type == CMSG_WARNING)
1214     {
1215         color = TimW32gNewConsole::WarningColor;
1216     }
1217     else if (type == CMSG_INFO && verbosity_level <= VERB_NORMAL)
1218     {
1219         color = TimW32gNewConsole::InfoColor;
1220     }
1221
1222     if (::IsWindow(hConsoleWnd))
1223     {
1224         auto pConsoleWindow = reinterpret_cast<TimW32gNewConsole::NewConsoleWindow*>(
1225             ::GetWindowLongPtr(::GetDlgItem(hConsoleWnd, IDC_EDIT), GWLP_USERDATA)
1226         );
1227
1228         if (pConsoleWindow)
1229         {
1230             pConsoleWindow->Write(color, str, true);
1231             return;
1232         }
1233     }
1234
1235     TimW32gNewConsole::GlobalNewConsoleBuffer.Append(color, str);
1236     TimW32gNewConsole::GlobalNewConsoleBuffer.AppendNewline();
1237 }
1238
1239 extern "C" void InitializeNewConsole(void)
1240 {
1241     WNDCLASSEX wc = {};
1242     wc.cbSize = sizeof(wc);
1243
1244     if (!::GetClassInfoEx(::GetModuleHandle(nullptr), TimW32gNewConsole::pClassName, &wc))
1245     {
1246         wc.style = CS_HREDRAW | CS_VREDRAW;
1247         wc.lpfnWndProc = &TimW32gNewConsole::NewConsoleWindow::WindowProc;
1248         wc.cbClsExtra = 0;
1249         wc.cbWndExtra = 0;
1250         wc.hInstance = ::GetModuleHandle(nullptr);
1251         wc.hIcon = nullptr;
1252         wc.hCursor = ::LoadCursor(nullptr, IDC_ARROW);
1253         wc.hbrBackground = nullptr;
1254         wc.lpszMenuName = nullptr;
1255         wc.lpszClassName = TimW32gNewConsole::pClassName;
1256         wc.hIconSm = nullptr;
1257
1258         ::RegisterClassEx(&wc);
1259     }
1260 }
1261
1262 extern "C" void NewConsoleClear(HWND hwnd)
1263 {
1264     auto pConsoleWindow = reinterpret_cast<TimW32gNewConsole::NewConsoleWindow*>(::GetWindowLongPtr(hwnd, GWLP_USERDATA));
1265
1266     if (pConsoleWindow)
1267     {
1268         pConsoleWindow->Clear();
1269     }
1270 }
1271
1272 extern "C" void NewConsoleWrite(HWND hwnd, LPCTSTR str)
1273 {
1274     auto pConsoleWindow = reinterpret_cast<TimW32gNewConsole::NewConsoleWindow*>(::GetWindowLongPtr(hwnd, GWLP_USERDATA));
1275
1276     if (pConsoleWindow)
1277     {
1278         pConsoleWindow->Write(str);
1279     }
1280 }
1281
1282 extern "C" void NewConsoleWriteV(HWND hwnd, LPCTSTR format, va_list args)
1283 {
1284     auto pConsoleWindow = reinterpret_cast<TimW32gNewConsole::NewConsoleWindow*>(::GetWindowLongPtr(hwnd, GWLP_USERDATA));
1285
1286     if (pConsoleWindow)
1287     {
1288         pConsoleWindow->WriteV(format, args);
1289     }
1290 }