Turbo Vision 2.0 - พอร์ตสมัยใหม่
(github.com/magiblot)- ย้าย เฟรมเวิร์ก Text UI แบบคลาสสิกกลับมาให้ใช้งานได้อีกครั้งบน Linux และ Windows พร้อมมอบพื้นฐานการพัฒนาร่วมที่รองรับ UTF-8 และสีแบบขยาย
- รักษา ความเข้ากันได้ระดับซอร์สโค้ด กับแอป Turbo Vision เดิมให้มากที่สุด โดยยังคงใช้ API ที่อิง
charต่อไป และให้ไลบรารีจัดการความแตกต่างของเทอร์มินัลในแต่ละแพลตฟอร์มไว้ภายใน - ผสานการป้อนข้อมูลและการแสดงผลแบบ Unicode เข้ากับทั้งเฟรมเวิร์ก เพื่อรองรับ อักขระกว้างสองช่อง, อักขระผสม, คีย์ลัดแบบหลายไบต์, รวมถึงการแก้ไขและการวาดผลบนพื้นฐาน UTF-8
- มี วิดเจ็ตพื้นฐาน อย่างหน้าต่างซ้อนทับ เมนู กล่องโต้ตอบ ช่องป้อนข้อมูล และแถบเลื่อน พร้อมรองรับพฤติกรรมสมัยใหม่ เช่น event loop, ตัวจับเวลา, คลิปบอร์ด, ล้อเมาส์, การปรับขนาด, และการแก้ไขไฟล์ขนาดใหญ่
- ก้าวข้าม API ยุคเก่าที่เน้น 16 สี ไปสู่การรองรับ สี 24 บิต, พาเลต xterm-256, คลิปบอร์ดของระบบ และคอนโซลหลายรูปแบบ ทำหน้าที่เป็นสะพานเชื่อมระหว่างโค้ด TUI รุ่นเก่ากับสภาพแวดล้อมเทอร์มินัลสมัยใหม่
ลักษณะของโปรเจกต์และทิศทางหลัก
- เป็น พอร์ตสมัยใหม่ของ Turbo Vision 2.0 ที่ทำให้เฟรมเวิร์ก Text UI แบบคลาสสิกกลับมาใช้งานได้บน Linux และ Windows พร้อมดึง UTF-8 และสีแบบขยายเข้ามาใช้
- ในช่วงแรกมุ่งเน้นที่การเพิ่มการรองรับ Linux การคงพฤติกรรมเดิมของ DOS/Windows และการรักษา ความเข้ากันได้ระดับซอร์สโค้ด ของแอป Turbo Vision รุ่นเก่า
- เพื่อความเข้ากันได้นี้ จึงมีการเขียน ฟังก์ชัน Borland C++ RTL บางส่วนขึ้นมาเอง
- ระหว่างเดือนกรกฎาคมถึงสิงหาคม 2020 ได้ผสาน การรองรับ Unicode แบบสมบูรณ์ เข้าไปในโครงสร้างเดิม และระหว่างนั้นก็ได้สร้างโปรแกรมแก้ไขข้อความ Turbo ขึ้นมาด้วย
- มีการห่อหุ้มเพื่อไม่ให้แอปพลิเคชันต้องเลี่ยงข้อแตกต่างของความสามารถเทอร์มินัลหรือจัดการเทอร์มินัล I/O โดยตรง ทำให้ไลบรารีเป็นฝ่ายดูดซับความต่างตามแพลตฟอร์ม
- มุ่งเน้นการเขียน Text UI ด้วยโค้ดชุดเดียวกันบน Linux และ Windows โดยยังคงใช้ API ที่อิง
charต่อไป แทนที่จะใช้wchar_tหรือTCHAR
ทำไมจึงมีประโยชน์
- มี คลาสวิดเจ็ต จำนวนมากให้ใช้ทันที เช่น หน้าต่างซ้อนทับที่ปรับขนาดได้ เมนูแบบดรอปดาวน์ กล่องโต้ตอบ ปุ่ม แถบเลื่อน ช่องป้อนข้อมูล เช็กบ็อกซ์ และปุ่มตัวเลือก
- แม้จะสร้างวิดเจ็ตเอง ก็ลดภาระในการต้องเขียนงานส่วนกลางซ้ำใหม่ เช่น การ dispatch เหตุการณ์ หรือการแสดงผลอักขระ Unicode แบบเต็มความกว้าง
- พยายามทำให้ได้ ผลลัพธ์แบบเดียวกัน มากที่สุดในแต่ละสภาพแวดล้อม
- แม้ในกรณีที่สีพื้นหลังแบบสว่างบน Linux console ต้องพึ่งพาแอตทริบิวต์ blink ไลบรารีก็จัดการแทนให้
- อาศัยการรองรับ UTF-8 ของ
setlocaleใน Microsoft RTL ทำให้บน Windows โค้ดอย่างstd::ifstream f("コンピュータ.txt");สามารถทำงานได้ตรงตามที่ตั้งใจ- บน Windows นั้น RTL จะเปลี่ยนชื่อไฟล์เป็นการเข้ารหัสของระบบทันที
การออกแบบ Unicode และการจัดการข้อความ
- เลือกใช้ UTF-8 เป็นการเข้ารหัสพื้นฐานสำหรับการป้อนข้อมูลและการแสดงผล
- เข้ากันได้กับชนิดข้อมูล
char *เดิม และตรงกับการเข้ารหัสของเทอร์มินัล I/O จึงลดการแปลงที่ไม่จำเป็น - ยังสอดคล้องกับแนวทางของ UTF-8 Everywhere Manifesto ด้วย
- เข้ากันได้กับชนิดข้อมูล
- เมื่อบิลด์ด้วย Borland C++ จะไม่รองรับ Unicode แต่การขยาย API ถูกออกแบบมาให้สามารถเขียน โค้ดที่ไม่ผูกกับการเข้ารหัส ได้
- ใน
KeyDownEventนอกจากcharScan.charCodeเดิมแล้ว ยังมีtext[4]และtextLengthเพิ่มเข้ามาเพื่อส่งต่อ ลำดับไบต์อินพุตแบบ UTF-8 โดยตรง- ใน
charScan.charCodeยังคงใส่อักขระตาม CP437 ไว้เช่นเดิมเพื่อความเข้ากันได้ย้อนหลัง - แนะนำให้ใช้งานผ่าน string view ที่ได้จาก
getText()
- ใน
- ตัวอย่างอินพุตแสดงให้เห็นชัดเจนว่าทั้ง Unicode และโค้ดแบบเก่าสามารถทำงานร่วมกันได้อย่างไร
- เมื่อป้อน
ñจะได้charCode=164 ('ñ'),text={'\xC3','\xB1'},textLength=2 - เมื่อป้อน
€ซึ่งไม่มีใน CP437 จะได้keyCode=0x0,charCode=0,text={'\xE2','\x82','\xAC'},textLength=3 - เมื่อป้อนคีย์ลัด
textLength=0จะว่างเปล่า
- เมื่อป้อน
- TScreenCell สามารถเก็บโค้ดพอยต์ UTF-8 และแอตทริบิวต์ขยายอย่างตัวหนา ขีดเส้นใต้ และตัวเอียงได้
- อักขระควบคุมแบบ ASCII จะถูกจัดการเหมือนอักขระของ code page
- UTF-8 ที่ถูกต้องจะแสดงผลตามเดิม ส่วน UTF-8 ที่ไม่ถูกต้องจะถูกจัดการเป็นอักขระของ code page
- สามารถผสม extended ASCII กับ UTF-8 ได้ จึงดีต่อความเข้ากันได้ย้อนหลัง แต่อาจให้ผลลัพธ์ที่ไม่คาดคิดได้เช่นกัน
- มีการจัดการ อักขระกว้างสองช่อง และ อักขระผสม แยกต่างหากด้วย
- อักขระผสมจะซ้อนทับอยู่บนอักขระก่อนหน้า
ZERO WIDTH JOINERU+200Dจะถูกละไว้เสมอ- หากเทอร์มินัลเคารพความกว้างอักขระตาม
wcwidthก็แทบจะไม่เกิดอาการกราฟิกเพี้ยนที่สังเกตได้ - ดูตัวอย่างได้ที่ Wide character display
การรองรับ Unicode API และวิวมาตรฐาน
-
API การวาดที่รองรับ Unicode
TDrawBuffer::moveChar,putCharจัดการchar cเป็น อักขระ code pagemoveStr,moveCStrรับTStringViewและจัดการสตริงตาม กฎของ UnicodemaxStrWidthอิงตาม จำนวนคอลัมน์ ไม่ใช่จำนวนไบต์strIndentก็อิงตาม จำนวนคอลัมน์ ไม่ใช่จำนวนไบต์ จึงใช้กับการเลื่อนแนวนอนได้- ค่าที่ส่งกลับคือ ความกว้างในการแสดงผล ของข้อความที่คัดลอก
moveBufยังคงมีไว้เพราะเป็นฟังก์ชันที่ใช้จัดการสตริงที่ไม่ใช่ null-terminated ก่อนมีการนำTStringViewเข้ามาcstrlen(TStringView s)คืนค่าความยาวที่แสดงผลโดยไม่รวม~ส่วนstrwidth(TStringView s)คืนค่าความยาวที่แสดงผลโดยรวม~
-
ทำให้ draw() เรียบง่ายขึ้นและลดบั๊ก
- เดิม
TFileViewer::draw()จะคัดลอก substring ไปยังบัฟเฟอร์ชั่วคราวก่อนแล้วค่อยเรียกmoveStrแต่วิธีนี้มีความเสี่ยงเรื่อง buffer overrun และปัญหาที่ขอบเขตของอักขระหลายไบต์ - หลังแก้ไขแล้วสามารถจัดการได้ด้วยบรรทัดเดียว
b.moveStr(0, p, c, size.x, delta.x);จึงไม่ต้องมีการคัดลอกระหว่างทาง- สามารถใช้
delta.xได้โดยตรงในหน่วยคอลัมน์ - คัดลอกสูงสุดเพียง
size.xคอลัมน์ จึงลดความจำเป็นในการคำนวณจำนวนไบต์และเงื่อนไขขอบเขตด้วยตนเอง
- สามารถใช้
- เดิม
-
วิวมาตรฐานที่รองรับการแสดงผล Unicode
TStaticText,TFrame,TStatusLine,THistoryViewer,THelpViewer,8c7dac2a,20f331e3,TListViewer,TMenuBox,TTerminal,TOutlineViewer,TFileViewerของtvdemo,TFilePaneของtvdirรองรับการแสดงผล Unicode- บางวิวรองรับทั้ง การเลื่อนแนวนอน และ การตัดคำขึ้นบรรทัดใหม่ ด้วย
-
วิวที่รองรับไปถึงการป้อนข้อมูล Unicode
TInputLine,cb489d42,TEditor, ปุ่มลัดของTMenuViewรองรับไปถึง การป้อนข้อมูล Unicode ของผู้ใช้- อินสแตนซ์
TEditorจะอยู่ใน โหมด UTF-8 โดยค่าเริ่มต้น และสามารถสลับเป็นโหมด single-byte ได้ด้วยCtrl+P - การสลับนี้จะเปลี่ยนเฉพาะ วิธีแสดงผล และ encoding ของข้อมูลนำเข้า โดยไม่เปลี่ยนเนื้อหาเอกสาร
- ยังไม่รองรับ ปุ่มลัดคีย์ที่เน้นแสดง ของ
TStatusLine,TButtonเป็นต้น
-
สตริงหลายภาษาสำหรับเมนูและแถบสถานะ
- สามารถใช้สตริงอย่าง
~Ñ~ello,階~毎~料入報最...,五劫~の~擦り切れ,העברית ~א~ינטרנט,~Alt-Ç~ Exitได้ตามเดิมในเมนูและแถบสถานะ - ดูผลลัพธ์บนหน้าจอได้ที่ Unicode Hello
- สามารถใช้สตริงอย่าง
โมเดลการทำงานแยกตามแพลตฟอร์ม
-
Unix
- ใช้การรองรับเทอร์มินัลที่อิงกับ Ncurses
- รองรับการเข้ารหัสเมาส์แบบ X10, SGR, modifyOtherKeys ของ xterm, fixterms ของ Paul Evans, keyboard protocol ของ Kitty,
win32-input-modeของ WSL Conpty, far2l, ตัวปรับแต่งTIOCLINUXของคอนโซล Linux และเมาส์ GPM - มี custom signal handler สำหรับกู้คืนสถานะของเทอร์มินัลก่อนที่โปรแกรมจะจบการทำงานแบบผิดปกติ
- หาก
stderrเป็น tty จะเก็บข้อมูลไว้ในบัฟเฟอร์เพื่อป้องกันหน้าจอเสียหาย แล้วค่อยแสดงผลเมื่อออกจากโปรแกรมหรือหยุดชั่วคราว- มีการจำกัดขนาดบัฟเฟอร์ ดังนั้นหากเต็ม การเขียน
stderrอาจล้มเหลว - หากต้องการเก็บทั้งหมด แนะนำให้รีไดเร็กต์
2>
- มีการจำกัดขนาดบัฟเฟอร์ ดังนั้นหากเต็ม การเขียน
- อ่านตัวแปรสภาพแวดล้อม
TERM,COLORTERM,ESCDELAY,TVISION_USE_STDIO- หาก
COLORTERM=truecolorหรือ24bitจะถือว่าเป็นสี 24 บิต - ค่าเริ่มต้นของ
ESCDELAYคือ10ms - หาก
TVISION_USE_STDIOไม่ว่าง จะทำ I/O ผ่านstdinและstdoutแทน/dev/tty
- หาก
-
Windows
- ใช้งานร่วมได้เฉพาะกับ Win32 Console API
- ใน terminal emulator ที่ไม่รองรับ จะเปิดหน้าต่างคอนโซลแยกให้อัตโนมัติ
- การจัดวางแอปพลิเคชันยึดตาม ขนาดหน้าต่างคอนโซล ไม่ใช่คอนโซลบัฟเฟอร์ และจะกู้คืนคอนโซลบัฟเฟอร์เมื่อออกหรือหยุดชั่วคราว
- หากไม่ใช่ Borland C++ จะเปลี่ยน code page ของคอนโซลเป็น UTF-8 ตอนเริ่มต้น และกู้คืนเมื่อสิ้นสุดการทำงาน
- Microsoft C runtime ก็จะถูกสลับเป็นโหมด UTF-8 ด้วย เพื่อลดความจำเป็นที่นักพัฒนาต้องใช้ตัวแปรแบบ
wchar_tโดยตรง - ในชุดค่าผสมของ legacy mode กับ bitmap font การแสดงผล Unicode อาจเพี้ยนได้ และหากตรวจพบจะพยายามเปลี่ยนฟอนต์เป็น
ConsolasหรือLucida Console- ดูตัวอย่างได้จาก photo
-
การปรับปรุงการทำงานร่วมกัน
- รองรับ สี 24 บิต, การอยู่ร่วมกับการรีไดเร็กต์
stdin/stdout/stderrและความเข้ากันได้กับไฟล์ help แบบ 32 บิต - ควบคุมอัตรารีเฟรชสูงสุดได้ด้วยตัวแปรสภาพแวดล้อม
TVISION_MAX_FPS- ค่าเริ่มต้นคือ
60 0คือปิดการจำกัด-1คือให้วาดทันทีทุกครั้งที่มีการเรียกTHardwareInfo::screenWrite
- ค่าเริ่มต้นคือ
- รองรับปุ่มเมาส์กลาง, ล้อเมาส์, ขนาดหน้าจอสูงสุด 32767 แถว/คอลัมน์ และ resize event
- หน้าต่างสามารถปรับขนาดจากมุมล่างซ้ายได้ และสามารถใช้ปุ่มกลางลากพื้นที่ว่างได้
- เมนูสามารถปิดได้ด้วยการคลิกที่รายการแม่ซ้ำอีกครั้ง และ scrollbar จะตอบสนองต่อ page scroll และอินพุตจากล้อเมาส์ระหว่างลาก
TInputLineจะไม่เลื่อนข้อความโดยไม่จำเป็นเมื่อมีการย้ายโฟกัสTFileViewerและTEditorรองรับ LF line ending โดยTEditorจะคงรูปแบบ line ending เดิมไว้ตอนบันทึก และไฟล์ใหม่จะใช้ CRLF เป็นค่าเริ่มต้นTEditorยังมีเมนูคลิกขวา, การเลื่อนแบบลากด้วยปุ่มกลาง, ปุ่มลบทีละคำ, การปรับปรุงปุ่ม Home และรองรับไฟล์ที่มีขนาด เกิน 64 KiBtvdemoมี applet สำหรับดู event และตัวเลือกเปลี่ยนลวดลายพื้นหลัง
- รองรับ สี 24 บิต, การอยู่ร่วมกับการรีไดเร็กต์
การเปลี่ยนแปลงของ event loop และ API
- ใช้ การบัฟเฟอร์การเขียนหน้าจอ และโดยทั่วไปจะส่งไปยังเทอร์มินัลหนึ่งครั้งต่อหนึ่งรอบของ active event loop
- หากต้องการอัปเดตทันทีใน busy loop สามารถเรียก
TScreen::flushScreen()ได้ TEventQueue::waitForEvents(int timeoutMs)จะรอ input event และระหว่างรอก็จะอัปเดตหน้าจอผ่านTScreen::flushScreen()TProgram::getEvent()จะเรียกสิ่งนี้ด้วยeventTimeoutMsซึ่งมีค่าเริ่มต้นเป็น20เพื่อหลีกเลี่ยง busy loop ที่ใช้ CPU 100%TEventQueue::wakeUp()เป็นเมธอดแบบ thread-safe สำหรับปลุก event loop จากเธรดอื่นTView::getEvent(TEvent &, int timeoutMs)ใช้ตั้งการรอแบบ timeout ในระดับ view ได้- มีการเพิ่ม
setTimer,killTimerซึ่งจะส่งevBroadcastและcmTimerExpiredเมื่อ timer หมดเวลา- หาก
periodMsติดลบ จะเป็น timer แบบครั้งเดียวและล้างออกอัตโนมัติ - timer event จะถูกสร้างใน
TProgram::idle()และจะถูกประมวลผลเฉพาะเมื่อไม่มี event จากคีย์บอร์ดหรือเมาส์
- หาก
TDrawBufferไม่ได้เป็นอาร์เรย์ความยาวคงที่อีกต่อไป และช่วยป้องกันการเข้าถึงนอกขอบเขตmove,grow,intersect,UnionของTRectจะคืนค่าTRect&เพื่อให้ chain ต่อได้TApplicationมีdosShell(),cascade(),tile()มาให้โดยค่าเริ่มต้น และจัดการcmDosShell,cmCascade,cmTileให้ในตัว- มีการเพิ่มชนิดข้อมูลอย่าง
TStringView,TSpan<T>,TDrawSurface,TSurfaceView,TClipboard THardwareInfo,TScreen,TEventQueueจะถูก initialize ตอนสร้างTApplicationตัวแรก ไม่ใช่ก่อนmain- มีการขยายความสามารถด้านอินพุตด้วย
evMouseWheel,mwUp,mwDown,mwLeft,mwRightmbMiddleButton,meTripleClick- เก็บข้อมูลปุ่มที่ถูกปล่อยไว้ใน
evMouseUp.buttons - สามารถทำ คีย์ลัดแบบหลายไบต์ ได้ด้วย
hotKeyStr,getAltCharStr,getCtrlCharStr - สามารถกำหนดคีย์คอมบิเนชันใหม่อย่าง
Shift+Alt+Upได้ด้วยTKey TInputLineรองรับ limitMode ของilMaxBytes,ilMaxWidth,ilMaxChars
การเชื่อมต่อคลิปบอร์ด
-
การผสานรวมกับคลิปบอร์ดของระบบ
- ก่อนหน้านี้มีเพียง คลิปบอร์ดภายใน ผ่าน static member
TEditor::clipboardและมีแค่TEditorเท่านั้นที่ใช้งานได้ - คลาส
TClipboardใหม่ใช้เข้าถึง คลิปบอร์ดของระบบ และหากเข้าถึงไม่ได้จะสลับไปใช้คลิปบอร์ดภายในแทน
- ก่อนหน้านี้มีเพียง คลิปบอร์ดภายใน ผ่าน static member
-
สภาพแวดล้อมที่รองรับ
- รองรับโดยตรงบน Windows, WSL และ macOS
- บน Unix ที่ไม่ใช่ macOS จำเป็นต้องมี dependency ภายนอก
- ในการรันระยะไกล สามารถทำงานได้ผ่าน X11 forwarding ของ
ssh -X, far2l และ putty4far2l, และเทอร์มินัลที่รองรับ OSC 52 - เทอร์มินัลอื่นบางตัว รองรับเฉพาะการคัดลอก
- การวางผ่าน
Ctrl+Shift+VหรือCmd+Vของ terminal emulator ยังคงทำได้เสมอแยกต่างหาก
-
API และการจัดการการวาง
- หากกำหนด
Uses_TClipboardก่อน include<tvision/tv.h>จะสามารถใช้TClipboardได้ setText(TStringView text)จะตั้งค่าเนื้อหาในคลิปบอร์ดของระบบ และถ้าเข้าถึงไม่ได้จะใช้คลิปบอร์ดภายในrequestText()จะ ร้องขอแบบ asynchronous เนื้อหาจากคลิปบอร์ดของระบบ และจะได้รับในภายหลังในรูปแบบ eventevKeyDownปกติ- สามารถแยกข้อความที่วางออกจากการกดปุ่มทั่วไปได้ด้วย แฟล็ก
kbPasteในkeyDown.controlKeyState - เพื่อช่วยลดความไม่มีประสิทธิภาพที่การวางข้อความยาวถูกประมวลผลเหมือนการกดปุ่มหลายพันครั้ง จึงมีการเพิ่ม
TView::textEvent(...)- อ่านข้อความ
evKeyDownที่ต่อเนื่องกันโดยรวบรวมลงในบัฟเฟอร์ของผู้ใช้ - เมื่อไม่ใช่ข้อความอีกต่อไป จะส่งต่อไปยังลูปถัดไปด้วย
putEvent()
- อ่านข้อความ
- หากกำหนด
-
การเชื่อมกับคำสั่งมาตรฐาน
TEditorและTInputLineตอบสนองต่อคำสั่งcmCut,cmCopy,cmPaste- แอปพลิเคชันต้องผูก
kbCtrlX,kbCtrlC,kbCtrlVเข้ากับแต่ละคำสั่ง เช่น ในแถบสถานะ - จะเปิดหรือปิดใช้งานคำสั่งที่เกี่ยวข้องโดยอัตโนมัติตามโฟกัสและสถานะการเลือก
โมเดลสีแบบขยายและความเข้ากันได้
- Turbo Vision API รองรับ สีแบบขยาย นอกเหนือจาก 16 สีเดิม
- 4-bit BIOS color attributes
- 24-bit RGB
- ดัชนีพาเลต 8-bit xterm-256color
- terminal default color
- หากเทอร์มินัลไม่รองรับรูปแบบสีบางแบบ Turbo Vision จะทำการ quantize เพื่อแสดงผล
- บนแพลตฟอร์มสมัยใหม่ มีการนำ TColorAttr มาใช้แทน
ucharและ TAttrPair มาใช้แทนushortTPaletteก็ใช้อาร์เรย์ของTColorAttrด้วยTView::mapColorถูกทำให้เป็นvirtualเพื่อให้สามารถกำหนดสีโดยข้ามระบบพาเลตแบบลำดับชั้นได้
- แบ่งชั้นการแสดงค่าสีด้วย
TColorBIOS,TColorRGB,TColorXTerm,TColorDesiredTColorAttrมีบิตมาสก์foreground,background,style- style มี
slBold,slItalic,slUnderline,slBlink,slReverse,slStrike slReverseเชื่อถือได้ไม่มาก จึงแนะนำให้ใช้reverseAttribute(TColorAttr attr)
- มี 3 วิธีในการใช้สีแบบขยายใน
TView- override
mapColorแล้ว ฮาร์ดโค้ด ตามแต่ละดัชนี - ส่ง
TColorAttrโดยตรง ให้TDrawBufferในdraw() - แก้ไขพาเลตของแอปพลิเคชันเอง
- override
TScreen::screenModeใช้เปิดเผยความสามารถของการแสดงผลsmMonoคือ monocolorsmBW80คือ grayscalesmCO80คืออย่างน้อย 16 สีsmColor256คืออย่างน้อย 256 สีsmColorHighคือสีที่มากกว่านั้น เช่น สี 24 บิต
- ความเข้ากันได้ย้อนหลังถูกออกแบบมาเพื่อคง API ร่วมที่ไม่มี
#ifdef- บน Borland C++ จะ typedef
TColorAttrและTAttrPairเป็นucharและushortตามลำดับ - บนแพลตฟอร์มสมัยใหม่ สามารถใช้แทนตำแหน่งเดิมของ
ucharและushortได้ - โค้ด legacy ยังสามารถคอมไพล์ได้เหมือนเดิม แต่ color attribute ที่ไม่ใช่ BIOS อาจสูญหายทันทีที่ถูกแปลงแบบปริยายเป็น
ucharหรือushort
- บน Borland C++ จะ typedef
- โค้ดในเมธอด
drawที่ใช้ushortร่วมกันทั้งกับคู่ดัชนีพาเลตและคู่ color attribute ยังคงทำงานได้ตามเดิม แต่หากต้องการคงสีแบบขยายไว้ ให้เปลี่ยนการประกาศตัวแปรจากushortเป็น TAttrPair TColorDialogไม่ได้ถูกออกแบบใหม่ จึงไม่สามารถใช้เป็น ตัวเลือกสีแบบขยาย ตอนรันไทม์ได้
การบิลด์, การแจกจ่าย, การผสานรวม
-
Linux และตระกูล Unix
- สามารถบิลด์ไลบรารีแบบสแตติก
libtvision.aได้ด้วย CMake และ GCC/Clang - จะสร้าง
hello,tvdemo,tvedit,tvdir,mmenu,paletteและ Help Compilertvhcมาพร้อมกัน - ข้อกำหนดคือ คอมไพเลอร์ที่รองรับ C++14,
libncurseswและlibgpmซึ่งเป็นตัวเลือกเสริม - การเชื่อมต่อกับคลิปบอร์ดขณะรันไทม์ใช้
xsel,xclip,wl-clipboard - ในบางสภาพแวดล้อมอาจต้องใช้
-ltinfowมิฉะนั้นอาจเกิด segmentation fault ตอนรัน- อีชูที่เกี่ยวข้อง: #11
- สามารถบิลด์ไลบรารีแบบสแตติก
-
Windows และทูลเชนอื่น ๆ
- การบิลด์ด้วย MSVC ใช้ไดเรกทอรีบิลด์แยกตามสถาปัตยกรรมเป้าหมาย และจะได้ผลลัพธ์เป็น
tvision.libพร้อมแอปตัวอย่าง - สามารถลิงก์ Microsoft runtime แบบสแตติกได้ด้วยตัวเลือก
TV_USE_STATIC_RTL - ไม่สามารถลิงก์ปะปนกันระหว่าง
/MTกับ/MDและไบนารีดีบักกับไม่ดีบักได้ - การใช้
<tvision/tv.h>ต้องใช้แฟล็ก/permissive-,/Zc:__cplusplusและหากใช้ CMake แบบซับโมดูลจะเปิดให้อัตโนมัติ - ระบุไว้ว่า Windows XP ก็ใช้งานได้ หากใช้เวอร์ชันและการตั้งค่า MSVC ที่เหมาะสม
- MinGW บิลด์ด้วย CMake คล้ายกับบน Linux และหากคอมไพเลอร์รองรับก็สามารถรันได้บน Windows XP ขึ้นไป
- สามารถบิลด์ไลบรารีสำหรับ DOS หรือ Windows ด้วย Borland C++ ได้เช่นกัน แต่ ไม่รองรับ Unicode
- ในสภาพแวดล้อม Borland C++ มีการเสนอให้ใช้ winevdm เป็นทางเลือก
- การบิลด์ด้วย MSVC ใช้ไดเรกทอรีบิลด์แยกตามสถาปัตยกรรมเป้าหมาย และจะได้ผลลัพธ์เป็น
-
วิธีผสานรวมโปรเจกต์และสถานะการแจกจ่าย
- ใน CMake มีให้สองวิธีคือ
find_package(tvision CONFIG)หรือadd_subdirectory(tvision) - ทั้งสองวิธีจะจัดการพาธ include และการลิงก์ที่จำเป็น เช่น Ncurses, GPM ให้อัตโนมัติ
- มีพอร์ต
tvisionอยู่ใน vcpkg - ขณะนี้ยังไม่มี stable release และแนะนำให้รายงานปัญหาที่พบระหว่างอัปเกรดตามคอมมิตล่าสุด
- บน Unix ตระกูลต่าง ๆ ต้องบิลด์แอปเดโมด้วยตนเอง
- ไบนารีสำหรับ Windows มีให้จาก Actions
examples-dos32.zip: ไฟล์รันได้ 32 บิตที่บิลด์ด้วย Borland C++, ไม่รองรับ Unicodeexamples-x86.zip: ไฟล์รันได้ 32 บิตที่บิลด์ด้วย MSVC, ต้องใช้ Windows Vista ขึ้นไปexamples-x64.zip: ไฟล์รันได้ 64 บิตที่บิลด์ด้วย MSVC, ต้องใช้ Windows Vista แบบ x64 ขึ้นไป
- ใน CMake มีให้สองวิธีคือ
ตัวอย่าง, เอกสาร, กรณีการใช้งาน
- แนะนำ Turbo Vision For C++ User's Guide เป็นจุดเริ่มต้น
- หลังจากเข้าใจพื้นฐานแล้ว สามารถใช้อ้างอิง Turbo Vision 2.0 Programming Guide ได้
- มีตัวอย่างเช่น
hello,tvdemo,tvedit,paletteเป็นต้น - สามารถดูรวมภาพหน้าจอได้ที่ here
- แอปพลิเคชันที่ระบุว่าใช้งานอยู่มีดังนี้
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ดีใจมากที่เห็นรีโพซิทอรีนี้ขึ้นหน้า front page และตอนนี้ก็กำลังทำ wrapper สำหรับรีโพนี้เองอยู่
ตอนนี้กำลังรัน Turbo Vision บน macOS ผ่าน .Net ซึ่งให้ความรู้สึกเหมือนเวทมนตร์พอสมควร
กำลังทำ API ระดับสูงขึ้น พร้อมทั้งครอบหรือปรับปรุง palette API ที่ค่อนข้างล้าสมัย และเพิ่มระบบ layout เข้าไปด้วย
ตอนนี้ยังทำอยู่แบบหนักมากในรีโพส่วนตัว โดยวันนี้ก็ยังไล่ปรับ palette ตาม surface ที่วางคอนโทรลไว้ แล้วพรุ่งนี้ก็ไปเก็บรายละเอียดส่วนอื่นต่อแบบนี้เรื่อย ๆ
ยังเหลืองานอย่างจัดระเบียบ layout และเพิ่มคอนโทรลพื้นฐานที่มาตรฐานยุคนี้ควรมี
ก่อนหน้านี้เคยใช้ Terminal.Gui เหมือนกัน แต่เพราะมันกำลังเปลี่ยนผ่านไป v2 เลยใช้งานให้ไม่มีบั๊กได้ค่อนข้างยาก และ Claude ก็แสดงให้เห็นชัดดีว่าถ้าสร้างไลบรารี TUI โดยไม่คำนึงถึงเทอร์มินัลจริง ๆ ไม่ควรทำอะไรบ้าง
เลยคิดมาตลอดว่าอยากได้ Turbo Vision เวอร์ชันทันสมัย แล้วก็มาพบรีโพนี้เข้า พอเห็นว่ารองรับ Unicode ด้วยก็รู้สึกขอบคุณมากจริง ๆ
https://www.remobjects.com/elements/oxygene/
https://www.remobjects.com/elements/
ฉันเองก็กำลังทำ .NET wrapper อยู่เหมือนกัน อาจจะไปได้ไม่ไกลเท่าไร แต่ตั้งใจจะเลียนแบบ Windows Forms API ให้ใกล้ที่สุด และอยากใส่ตัวออกแบบ TUI แบบ drag-and-drop เข้าไปด้วย
ตัวอย่างอยู่ที่นี่: https://github.com/brianluft/terminalforms/tree/main/src/TerminalFormsDemo
งานเชื่อมต่อยาก ๆ ฝั่ง C++ ส่วนใหญ่จัดการไว้ตรงนี้: https://github.com/brianluft/terminalforms/tree/main/src/tfcore
ผม export ฟังก์ชัน C แบบเรียบง่ายไว้เพื่อให้เรียกผ่าน P/Invoke ได้ แล้วให้ฝั่ง C# เน้นไปที่การจัดโครงสร้างคลาสเป็นหลัก
ตอนแรกผมพยายามยืนกรานว่าสิ่งที่ทำได้ใน C++ จะต้องทำได้ใน C# ทั้งหมด แต่สุดท้ายมันซับซ้อนเกินไป ถึงขั้นเอา placement new มาใส่อ็อบเจ็กต์ C++ ลงในบัฟเฟอร์ของ C# จนเกือบกลายเป็นว่าฝั่ง C# สืบทอดคลาส C++ ได้จริง ๆ แล้วการออกแบบก็พังไปเลย
สุดท้ายก็เปลี่ยนมาใช้แนวทางที่ตรงไปตรงมามากกว่า ยืดหยุ่นน้อยลงแต่เรียบง่ายกว่ามาก และเก็บความยืดหยุ่นไว้ที่ฝั่ง C# แทน
อยากรู้ว่าคุณจัดระบบ P/Invoke system ของคุณยังไง
คงช่วยไม่ให้ผมไปพยายามเปล่าประโยชน์แบบทำแอปให้ GEOS หรือไปเข้าทีม Hurd แบบคนเดียวได้ดี
เคยลองใช้ Terminal.Gui แต่รู้สึกถูกใจฝั่ง TV มากกว่าเลยเคยคิดจะทำ wrapper เหมือนกัน ถ้าเปิดเผยเมื่อไรอยากดูมากจริง ๆ
เส้นทางการเขียนโปรแกรมของผมเริ่มต้นแบบตรงตัวจาก ถังขยะ ในยุค 90
ผมไปเจอ หนังสือ Turbo Vision ที่มีคนทิ้งไว้ และตกหลุมรัก TUI สีน้ำเงินที่ใคร ๆ ก็สร้างได้ทันที
เวอร์ชันดั้งเดิมอยู่ใน Turbo Pascal 6 และพอร์ต C++ ออกมาทีหลัง
เพราะงั้นนี่ก็คือพอร์ตแบบทันสมัยของพอร์ตอีกที
Borland ก็มีเฟรมเวิร์กอื่นที่คล้ายกัน โดย OWL เดิมทีเริ่มจากฝั่ง Turbo Pascal for Windows 1.5 ก่อน และเครื่องมือหลายส่วนของ C++ Builder ก็จริง ๆ เขียนด้วย Delphi
Object Pascal ใน Turbo Pascal 5.5 และ Turbo Vision ในเวอร์ชัน 6 คือจุดเริ่มต้น OOP ของผม และรู้สึกว่าโชคดีมากที่ได้เริ่มเส้นทางนั้นแบบนั้น
แม้อยู่ในสภาพแวดล้อมอย่าง MS-DOS ก็ยังเรียนรู้ข้อดีของ OOP และเฟรมเวิร์กแบบ Turbo Vision ได้อย่างเต็มที่
ตอนที่ Borland ออก Turbo Pascal, Turbo C++, TurboVision มานั้น เหมือนจักรวาลแห่งความเป็นไปได้เปิดกว้างออกมาตรงหน้า
คอมไพเลอร์ก็ยอดเยี่ยม คู่มือก็เหมือนงานศิลปะ เสียดายที่ไม่ได้เก็บหนังสือพวกนั้นไว้จนถึงตอนนี้
มันคือสมบัติทางวัฒนธรรมชัด ๆ
ช่วงต้นยุค 90 ผมเรียน C/C++ ด้วยตัวเองแทบจะจากการอ่านแต่กองหนังสือ Borland ที่มากับ Turbo C++ อย่างเดียว ทุกวันนี้แทบจินตนาการไม่ออกเลยว่าจะมีใครเรียนจากแค่อ่านคู่มืออ้างอิงแบบนั้น
เฟรมเวิร์ก TUI ใหม่ ๆ มักให้ความรู้สึกว่าขาดอะไรบางอย่างอยู่เสมอ ตอนนี้เลยจะลองกลับมาใช้ตัวนี้อีกครั้งเพื่อดูว่าที่คิดนั้นเป็นแค่ความคิดถึงหรือเปล่า
ผมจะเอามันไปใส่ในเครื่องมือตัวถัดไป และอยากปรบมือดัง ๆ ให้คนที่สร้างมันขึ้นมา
นอกจาก GW-BASIC กับ MS-DOS แล้ว ทุกอย่างเป็น Borland หมด ทั้ง Turbo BASIC, Turbo Pascal, Turbo C++ for MS-DOS and Windows 3.x, Turbo Vision, OWL
ผมมาใช้ VC++ ก็ราว ๆ เวอร์ชัน 5 ซึ่ง MFC ดูจืดชืดไปเลยเมื่อเทียบกับของ Borland
ทุกวันนี้ก็ยังมีไม่กี่อย่างที่ตาม ความสามารถด้าน RAD ของ C++ Builder ได้ทัน และ .NET เองก็ใช้เวลานานพอควรกว่าจะจัดการเรื่องการเขียนโค้ดระดับล่างแบบ Delphi และเรื่อง AOT ได้ลงตัว
ผมว่าเราควรยื่น Turbo Pascal 7 สำหรับ MS-DOS กับ Delphi รุ่นล่าสุดให้คนที่พัฒนา Go, C++, Rust ไปคนละหลายชุด
Turbo Vision 2.0 ยังใช้งานได้จริงค่อนข้างมากจนผมเคยใช้ทำงานต้นแบบเมื่อปีก่อน
ผมพยายามทำฟรอนต์เอนด์ Turbo Vision ให้ดีบักเกอร์ LLDB เพื่อให้ทำงานเหมือน Turbo Debugger ของ Borland และส่วนใหญ่ก็ออกมาตามที่หวัง
มันน่าทึ่งมากที่เหมือนรับช่วงต่อจากจุดที่ค้างไว้ในปี 199x ได้ตรง ๆ และยังคอมไพล์กับรันโค้ดจากปี 1993 ได้โดยแทบไม่มีปัญหา
ตัวแก้ไขภายในก็มีเวอร์ชันที่ดีกว่าซึ่งใช้ Scintilla เป็นฐานและมีฟีเจอร์อย่าง syntax highlighting ด้วย แต่สิ่งที่ผมอยากดัดแปลงกลับทำได้ไม่ค่อยดี เลยคงต้องไปขอความช่วยเหลือจากผู้เขียน
อย่างไรก็ตาม ถ้ามองในความหมายของ documentation แบบองค์ความรู้สาธารณะสมัยใหม่ มันยังมีน้อยอยู่ เลยถาม Stack Overflow หรือ AI ได้ยาก ต้องเรียนรู้จากโค้ดตัวอย่างและกลับไปอ่านหนังสือ Turbo Vision อยู่ซ้ำ ๆ แบบสมัยก่อน
การจัด layout ด้วยมือค่อนข้างน่าเบื่อ ถ้ามี auto layout แบบ Qt ก็น่าจะดี และก็คิดถึง splitter อยู่บ้าง แต่ดูแล้วคงทำเองไม่ยาก
อีกอย่างที่ทำให้แปลกใจคือ TV จริง ๆ แล้วเล็กและกะทัดรัดมาก ตอนยุค 90 มันดูยิ่งใหญ่มหาศาลกว่านี้ในความทรงจำ
โดยรวมแล้วงานปรับให้ทันสมัยทำออกมาได้ดีมาก และผมชอบมันมาก
Turbo Vision เดิมมีเอกสารคุณภาพสูงจำนวนมากอยู่แล้ว
กลับกัน ผมว่าของฝั่งสมัยใหม่นี่แหละที่เอกสารไม่ค่อยพอ
https://archive.org/details/bitsavers_borlandTurrogrammingGuide1992_25707423
แค่เห็น cmake directive เต็มไปหมดก็ชวนให้อยากย้อนเวลากลับไป
สมัย Turbo C หรือ Pascal แค่กด F9 ก็รันได้แล้ว
ในอีกมุมหนึ่งมันก็สะท้อนความไร้ประสิทธิภาพของ toolchain พวกเราเหมือนกัน
ยุคนี้มันควรจะแค่ชี้ไปที่คอมไพเลอร์ออนไลน์แล้วรันได้เลย หรือดาวน์โหลดมา เปิดโฟลเดอร์เดียวแล้วรันได้ จบ ไม่ควรกลายเป็นพิธีกรรมมากกว่าเป็นเครื่องมือ
./configure && make && make install นี่แหละควรยังเป็น gold standard อยู่
นี่เป็นแค่หนึ่งใน พอร์ต/โคลนของ Turbo Vision เท่านั้น
ฝั่ง C++ ยังมีตัวนี้ด้วย: https://github.com/kloczek/tvision
เวอร์ชันที่รวมอยู่ใน FreePascal/Lazarus เขียนด้วย Pascal และก็มีเวอร์ชัน Rust อยู่ตัวหนึ่งที่ดูเหมือนจะ vibe-coded อยู่บ้าง: https://github.com/aovestdipaperino/turbo-vision-4-rust
พอเอาไปรันในเทอร์มินัล มันจะหายไปนิดหน่อยจากความรู้สึกหลักที่ เมาส์บนหน้าจอโหมดข้อความ เคยให้ไว้
บนหน้าจอโหมดข้อความจริง ๆ มันไม่ได้เป็นตัวชี้เมาส์ แต่จะดูเหมือนบล็อกสีเหลืองที่ขยับได้ด้วยเมาส์มากกว่า
สงสัยว่ามีใครเคยลองรันมันบนโหมดข้อความความละเอียดสูงของ Linux พร้อม GPM บ้างไหม
มันแค่กลับสีของเซลล์ที่อยู่ใต้ตัวชี้ และเพราะหน้าต่างหลักที่กินพื้นที่เกือบทั้งจอส่วนใหญ่มักเป็นสีน้ำเงินเข้ม ผลเลยออกมาคล้ายบล็อกสีเหลืองสว่างอยู่บ่อย ๆ
ขอแนะนำตอนล่าสุดของ Wookash podcast ที่พูดถึง Chuck Jazdzewski
เขาเป็นหนึ่งในทีมที่สร้าง Turbo Vision ดั้งเดิม และยังเล่าเรื่องระบบนิเวศโดยรอบไว้เยอะมากด้วย
ผมยังอยากได้ Turbo Vision ของจริง คือเวอร์ชัน Pascal มากกว่าเวอร์ชัน C++ อยู่ดี
เวอร์ชัน C++ ให้ความรู้สึกเหมือนเป็นการย้าย Pascal เวอร์ชันหนึ่งมาอีกที
ตัวอย่างเช่น ใน Pascal นั้น
usesเป็นคีย์เวิร์ด แต่การ include โมดูลด้วย#defineยังไงก็ให้ความรู้สึกเหมือนเป็นการแฮ็กถึงตอนนี้ความต่างพวกนี้อาจไม่ใช่เรื่องใหญ่แล้วก็เถอะ
IDE โหมดข้อความก็ใช้ Free Vision
https://wiki.lazarus.freepascal.org/images/1/19/Userscreen.png
แต่ความต่างสำคัญคือ Free Vision กับ Turbo Vision ใช้ชนิด
objectจากยุค Turbo Pascal 5.5 ไม่ใช่classแบบ Delphiclassทำให้ทำพวกการทำซีเรียลไลซ์อัตโนมัติได้ง่ายกว่าด้วย RTTI แต่objectไม่มีสิ่งนั้น ถ้าจะจำแนกชนิดต่าง ๆ ตอนรันไทม์ก็ต้องทำซีเรียลไลซ์แบบแมนนวล โดยอาศัยการลงทะเบียน VMT pointer ที่อยู่ ณ offset คงที่ของ object pointerแม้ Free Pascal จะเพิ่มความสะดวกอย่าง private/protected/public และ property ให้กับ
objectบ้าง แต่ Free Vision ก็ไม่ใช้ส่วนขยายนั้น เพราะต้องคง API ดั้งเดิมของ Turbo Vision ไว้