9 คะแนน โดย GN⁺ 3 일 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ย้าย เฟรมเวิร์ก 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 JOINER U+200D จะถูกละไว้เสมอ
    • หากเทอร์มินัลเคารพความกว้างอักขระตาม wcwidth ก็แทบจะไม่เกิดอาการกราฟิกเพี้ยนที่สังเกตได้
    • ดูตัวอย่างได้ที่ Wide character display

การรองรับ Unicode API และวิวมาตรฐาน

  • API การวาดที่รองรับ Unicode

    • TDrawBuffer::moveChar, putChar จัดการ char c เป็น อักขระ code page
    • moveStr, moveCStr รับ TStringView และจัดการสตริงตาม กฎของ Unicode
      • maxStrWidth อิงตาม จำนวนคอลัมน์ ไม่ใช่จำนวนไบต์
      • 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

  • วิวที่รองรับไปถึงการป้อนข้อมูล 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 KiB
    • tvdemo มี applet สำหรับดู event และตัวเลือกเปลี่ยนลวดลายพื้นหลัง

การเปลี่ยนแปลงของ 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, mwRight
    • mbMiddleButton, meTripleClick
    • เก็บข้อมูลปุ่มที่ถูกปล่อยไว้ใน evMouseUp.buttons
    • สามารถทำ คีย์ลัดแบบหลายไบต์ ได้ด้วย hotKeyStr, getAltCharStr, getCtrlCharStr
    • สามารถกำหนดคีย์คอมบิเนชันใหม่อย่าง Shift+Alt+Up ได้ด้วย TKey
    • TInputLine รองรับ limitMode ของ ilMaxBytes, ilMaxWidth, ilMaxChars

การเชื่อมต่อคลิปบอร์ด

  • การผสานรวมกับคลิปบอร์ดของระบบ

    • ก่อนหน้านี้มีเพียง คลิปบอร์ดภายใน ผ่าน static member TEditor::clipboard และมีแค่ TEditor เท่านั้นที่ใช้งานได้
    • คลาส TClipboard ใหม่ใช้เข้าถึง คลิปบอร์ดของระบบ และหากเข้าถึงไม่ได้จะสลับไปใช้คลิปบอร์ดภายในแทน
  • สภาพแวดล้อมที่รองรับ

    • รองรับโดยตรงบน 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 เนื้อหาจากคลิปบอร์ดของระบบ และจะได้รับในภายหลังในรูปแบบ event evKeyDown ปกติ
    • สามารถแยกข้อความที่วางออกจากการกดปุ่มทั่วไปได้ด้วย แฟล็ก 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 มาใช้แทน ushort
    • TPalette ก็ใช้อาร์เรย์ของ TColorAttr ด้วย
    • TView::mapColor ถูกทำให้เป็น virtual เพื่อให้สามารถกำหนดสีโดยข้ามระบบพาเลตแบบลำดับชั้นได้
  • แบ่งชั้นการแสดงค่าสีด้วย TColorBIOS, TColorRGB, TColorXTerm, TColorDesired
    • TColorAttr มีบิตมาสก์ foreground, background, style
    • style มี slBold, slItalic, slUnderline, slBlink, slReverse, slStrike
    • slReverse เชื่อถือได้ไม่มาก จึงแนะนำให้ใช้ reverseAttribute(TColorAttr attr)
  • มี 3 วิธีในการใช้สีแบบขยายใน TView
    • override mapColor แล้ว ฮาร์ดโค้ด ตามแต่ละดัชนี
    • ส่ง TColorAttr โดยตรง ให้ TDrawBuffer ใน draw()
    • แก้ไขพาเลตของแอปพลิเคชันเอง
  • TScreen::screenMode ใช้เปิดเผยความสามารถของการแสดงผล
    • smMono คือ monocolor
    • smBW80 คือ grayscale
    • smCO80 คืออย่างน้อย 16 สี
    • smColor256 คืออย่างน้อย 256 สี
    • smColorHigh คือสีที่มากกว่านั้น เช่น สี 24 บิต
  • ความเข้ากันได้ย้อนหลังถูกออกแบบมาเพื่อคง API ร่วมที่ไม่มี #ifdef
    • บน Borland C++ จะ typedef TColorAttr และ TAttrPair เป็น uchar และ ushort ตามลำดับ
    • บนแพลตฟอร์มสมัยใหม่ สามารถใช้แทนตำแหน่งเดิมของ uchar และ ushort ได้
    • โค้ด legacy ยังสามารถคอมไพล์ได้เหมือนเดิม แต่ color attribute ที่ไม่ใช่ BIOS อาจสูญหายทันทีที่ถูกแปลงแบบปริยายเป็น uchar หรือ ushort
  • โค้ดในเมธอด draw ที่ใช้ ushort ร่วมกันทั้งกับคู่ดัชนีพาเลตและคู่ color attribute ยังคงทำงานได้ตามเดิม แต่หากต้องการคงสีแบบขยายไว้ ให้เปลี่ยนการประกาศตัวแปรจาก ushort เป็น TAttrPair
  • TColorDialog ไม่ได้ถูกออกแบบใหม่ จึงไม่สามารถใช้เป็น ตัวเลือกสีแบบขยาย ตอนรันไทม์ได้

การบิลด์, การแจกจ่าย, การผสานรวม

  • Linux และตระกูล Unix

    • สามารถบิลด์ไลบรารีแบบสแตติก libtvision.a ได้ด้วย CMake และ GCC/Clang
    • จะสร้าง hello, tvdemo, tvedit, tvdir, mmenu, palette และ Help Compiler tvhc มาพร้อมกัน
    • ข้อกำหนดคือ คอมไพเลอร์ที่รองรับ 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 เป็นทางเลือก
  • วิธีผสานรวมโปรเจกต์และสถานะการแจกจ่าย

    • ใน CMake มีให้สองวิธีคือ find_package(tvision CONFIG) หรือ add_subdirectory(tvision)
    • ทั้งสองวิธีจะจัดการพาธ include และการลิงก์ที่จำเป็น เช่น Ncurses, GPM ให้อัตโนมัติ
    • มีพอร์ต tvision อยู่ใน vcpkg
    • ขณะนี้ยังไม่มี stable release และแนะนำให้รายงานปัญหาที่พบระหว่างอัปเกรดตามคอมมิตล่าสุด
    • บน Unix ตระกูลต่าง ๆ ต้องบิลด์แอปเดโมด้วยตนเอง
    • ไบนารีสำหรับ Windows มีให้จาก Actions
      • examples-dos32.zip: ไฟล์รันได้ 32 บิตที่บิลด์ด้วย Borland C++, ไม่รองรับ Unicode
      • examples-x86.zip: ไฟล์รันได้ 32 บิตที่บิลด์ด้วย MSVC, ต้องใช้ Windows Vista ขึ้นไป
      • examples-x64.zip: ไฟล์รันได้ 64 บิตที่บิลด์ด้วย MSVC, ต้องใช้ Windows Vista แบบ x64 ขึ้นไป

ตัวอย่าง, เอกสาร, กรณีการใช้งาน

  • แนะนำ Turbo Vision For C++ User's Guide เป็นจุดเริ่มต้น
  • หลังจากเข้าใจพื้นฐานแล้ว สามารถใช้อ้างอิง Turbo Vision 2.0 Programming Guide ได้
  • มีตัวอย่างเช่น hello, tvdemo, tvedit, palette เป็นต้น
  • สามารถดูรวมภาพหน้าจอได้ที่ here
  • แอปพลิเคชันที่ระบุว่าใช้งานอยู่มีดังนี้
    • Turbo: โปรแกรมแก้ไขข้อความแบบ proof-of-concept
    • tvterm: โปรแกรมจำลองเทอร์มินัลแบบ proof-of-concept
    • TMBASIC: ภาษาโปรแกรมสำหรับสร้างแอปพลิเคชันคอนโซล

1 ความคิดเห็น

 
GN⁺ 3 일 전
ความคิดเห็นจาก Hacker News
  • ดีใจมากที่เห็นรีโพซิทอรีนี้ขึ้นหน้า front page และตอนนี้ก็กำลังทำ wrapper สำหรับรีโพนี้เองอยู่
    ตอนนี้กำลังรัน Turbo Vision บน macOS ผ่าน .Net ซึ่งให้ความรู้สึกเหมือนเวทมนตร์พอสมควร
    กำลังทำ API ระดับสูงขึ้น พร้อมทั้งครอบหรือปรับปรุง palette API ที่ค่อนข้างล้าสมัย และเพิ่มระบบ layout เข้าไปด้วย
    ตอนนี้ยังทำอยู่แบบหนักมากในรีโพส่วนตัว โดยวันนี้ก็ยังไล่ปรับ palette ตาม surface ที่วางคอนโทรลไว้ แล้วพรุ่งนี้ก็ไปเก็บรายละเอียดส่วนอื่นต่อแบบนี้เรื่อย ๆ
    ยังเหลืองานอย่างจัดระเบียบ layout และเพิ่มคอนโทรลพื้นฐานที่มาตรฐานยุคนี้ควรมี
    ก่อนหน้านี้เคยใช้ Terminal.Gui เหมือนกัน แต่เพราะมันกำลังเปลี่ยนผ่านไป v2 เลยใช้งานให้ไม่มีบั๊กได้ค่อนข้างยาก และ Claude ก็แสดงให้เห็นชัดดีว่าถ้าสร้างไลบรารี TUI โดยไม่คำนึงถึงเทอร์มินัลจริง ๆ ไม่ควรทำอะไรบ้าง
    เลยคิดมาตลอดว่าอยากได้ Turbo Vision เวอร์ชันทันสมัย แล้วก็มาพบรีโพนี้เข้า พอเห็นว่ารองรับ Unicode ด้วยก็รู้สึกขอบคุณมากจริง ๆ

    • Oxygene เป็นส่วนหนึ่งของชุดผลิตภัณฑ์ Elements ของ RemObjects ดังนั้นนอกจาก Oxygene สาย Pascal แล้ว ยังผสมใช้ภาษาอื่นยอดนิยมได้ด้วย และพาไปลง Windows, macOS, Linux, Android ฯลฯ ได้
      https://www.remobjects.com/elements/oxygene/
      https://www.remobjects.com/elements/
    • ฉันก็เคยทำงานกับ พอร์ต tvision นี้เหมือนกัน และทุกครั้งที่ไปลองเฟรมเวิร์ก TUI ใหม่ ๆ สุดท้ายก็จะรู้สึกว่า Turbo Vision ดีกว่าอยู่ดี
      ฉันเองก็กำลังทำ .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 ของคุณยังไง
    • พอได้เล่นไลบรารี TV นี้แล้วมันช่วยเกา ความคิดถึง ได้ดีมาก เลยสนุกสุด ๆ
      คงช่วยไม่ให้ผมไปพยายามเปล่าประโยชน์แบบทำแอปให้ 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 ได้อย่างเต็มที่

    • เรื่องน่าสนใจคือ Free Vision เคยเกิดจากมีคนเอาเวอร์ชัน C++ ที่ปล่อยเป็น public domain มานั่งแปลด้วยมือกลับไปเป็น Object/Free Pascal อีกที
    • OWL นี่ล้ำยุคจริง ๆ
  • ตอนที่ Borland ออก Turbo Pascal, Turbo C++, TurboVision มานั้น เหมือนจักรวาลแห่งความเป็นไปได้เปิดกว้างออกมาตรงหน้า
    คอมไพเลอร์ก็ยอดเยี่ยม คู่มือก็เหมือนงานศิลปะ เสียดายที่ไม่ได้เก็บหนังสือพวกนั้นไว้จนถึงตอนนี้
    มันคือสมบัติทางวัฒนธรรมชัด ๆ

    • คู่มือ พวกนั้นยอดเยี่ยมจริง ๆ
      ช่วงต้นยุค 90 ผมเรียน C/C++ ด้วยตัวเองแทบจะจากการอ่านแต่กองหนังสือ Borland ที่มากับ Turbo C++ อย่างเดียว ทุกวันนี้แทบจินตนาการไม่ออกเลยว่าจะมีใครเรียนจากแค่อ่านคู่มืออ้างอิงแบบนั้น
    • Turbo Vision เป็นเหมือน มาตรฐานทองคำ สำหรับผมมานานมาก
      เฟรมเวิร์ก TUI ใหม่ ๆ มักให้ความรู้สึกว่าขาดอะไรบางอย่างอยู่เสมอ ตอนนี้เลยจะลองกลับมาใช้ตัวนี้อีกครั้งเพื่อดูว่าที่คิดนั้นเป็นแค่ความคิดถึงหรือเปล่า
      ผมจะเอามันไปใส่ในเครื่องมือตัวถัดไป และอยากปรบมือดัง ๆ ให้คนที่สร้างมันขึ้นมา
    • มีช่วงหนึ่งที่ผม Borland ทั้งใจ
      นอกจาก 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 พวกเราเหมือนกัน
    ยุคนี้มันควรจะแค่ชี้ไปที่คอมไพเลอร์ออนไลน์แล้วรันได้เลย หรือดาวน์โหลดมา เปิดโฟลเดอร์เดียวแล้วรันได้ จบ ไม่ควรกลายเป็นพิธีกรรมมากกว่าเป็นเครื่องมือ

    • การคอมไพล์ซอฟต์แวร์บน Unix สมัยใหม่เคยเป็นปัญหาที่แก้ได้แล้วช่วงหนึ่ง
      ./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 ยังไงก็ให้ความรู้สึกเหมือนเป็นการแฮ็ก
    ถึงตอนนี้ความต่างพวกนี้อาจไม่ใช่เรื่องใหญ่แล้วก็เถอะ

    • Free Vision ที่มากับ Free Pascal ก็แทบจะทำหน้าที่นั้นอยู่แล้ว
      IDE โหมดข้อความก็ใช้ Free Vision
      https://wiki.lazarus.freepascal.org/images/1/19/Userscreen.png
      แต่ความต่างสำคัญคือ Free Vision กับ Turbo Vision ใช้ชนิด object จากยุค Turbo Pascal 5.5 ไม่ใช่ class แบบ Delphi
      class ทำให้ทำพวกการทำซีเรียลไลซ์อัตโนมัติได้ง่ายกว่าด้วย RTTI แต่ object ไม่มีสิ่งนั้น ถ้าจะจำแนกชนิดต่าง ๆ ตอนรันไทม์ก็ต้องทำซีเรียลไลซ์แบบแมนนวล โดยอาศัยการลงทะเบียน VMT pointer ที่อยู่ ณ offset คงที่ของ object pointer
      แม้ Free Pascal จะเพิ่มความสะดวกอย่าง private/protected/public และ property ให้กับ object บ้าง แต่ Free Vision ก็ไม่ใช้ส่วนขยายนั้น เพราะต้องคง API ดั้งเดิมของ Turbo Vision ไว้