[C++] 343 GUI上の座標を自動クリック macOSのApplication Services

[M1 Mac, MacOS Ventura 13.3.1, clang 14.0.3]

BBS閲覧アプリで右上のスレッドタイトルをクリックして、右下のスレッド欄をホバーしてもJavaScriptが機能せず、Fl_WebView上を1回クリックする必要があります。

このクリックを自動でさせるコードを書きました。macOSのApplication ServicesというAPIを使います。

これはCarbon APIに分類され、Mac OS(今はmacOS、その前はMac OS X、さらに前がMac OS)用のフレームワークをmacOSに移植したものです。

Objective-Cで書かれているために汎用性が低いCocoa APIとは異なり、C言語やC++で書かれているのでライブラリとしてそのまま使えます。

今回はFl_WebViewへの自動クリックに使いましたが、スレッド選択領域に戻る際にクリックが必要になるという副作用が生じたため採用は見送りました。

Carbon APIについては、いずれ切り捨てられてしまう可能性があるものの、それまでは活用する価値はあると思います。

#include <ApplicationServices/ApplicationServices.h>

Fl_Window* win; // 親ウィジェット
Fl_Window* win2; // 子ウィジェット

// 親ウィジェットは絶対座標
int x = win ->x();
int y = win ->y();
cout << "win x座標: " << x << " y座標: " << y << endl;

// 子ウィジェットは相対座標
int x2 = win2 ->x();
int y2 = win2 ->y();
cout << "win2 x座標: " << x_win << " y座標: " << y_win << endl;

// クリックイベントを作成
CGEventRef clickEvent = CGEventCreateMouseEvent(
    NULL, kCGEventLeftMouseDown,
    CGPointMake(x + x2 +5, y + y2 +5),
    kCGMouseButtonLeft
);

// イベントを送信
CGEventPost(kCGHIDEventTap, clickEvent);

// イベントを解放
CFRelease(clickEvent);

[C++] 342 BBS閲覧アプリの製作 その28 スレッドタイトル検索機能実装

[M1 Mac, MacOS Ventura 13.3.1, clang 14.0.3]

これは簡単に実装できました。15分位でしょうか。

これで基本的な機能は一通り備わったように思います。ボードによってはまれにスレッドタイトル読込で落ちてしまったりしますが、ぼちぼち直していきます。

ソースファイルが13個になり分かりにくくなってきたので、複数フォルダへの振り分けを検討しています。

高度な検索にかけたり、過去ログも含めて管理するのでしたらMySQLなどデータベースの出番もあるのでしょうが、今の用途であれば都度2次元ベクター化で事足りますね。

#include "cppstd.h" // 自製ライブラリ
#include "MyTable.h"

extern vector<tuple<string, string, string, string>> numTitlePostnumID;
extern Fl_Input* searchInput;
extern MyTable* table;

void threadSearch(Fl_Widget *w, void*){
    vector<tuple<string, string, string, string>> searchResult;
    const char* word = searchInput -> value();

    for (const auto& tuple : numTitlePostnumID) {
        string title = std::get<1>(tuple);
        if (title.find(word) != string::npos) {
            searchResult.push_back(make_tuple(std::get<0>(tuple), std::get<1>(tuple), std::get<2>(tuple), std::get<3>(tuple)));
        }
    }

    int hitNum = searchResult.size();
    numTitlePostnumID = searchResult;

    table->SetSize(hitNum, 5);
}

[C++] 341 BBS閲覧アプリの製作 その27 リンク画像をポップアップ表示 JavaScript

[M1 Mac, MacOS Ventura 13.3.1, clang 14.0.3]

画像のURLをマウスオーバーするとぼかし画像がポップアップ表示され、クリックすると加工無しの画像がポップアップ表示されるようにしました。

ここが山場になるかと構えていましたが、思っていたよりもあっさりでした。それでもChatGPTを駆使しながら4時間程度掛かっています。

最も手こずったのはクリックして指定サイズでポップアップさせるところでしょうか。どうしても普通にリンクを踏む形になって大きいサイズでの埋め込み表示になってしまいました。結局、event.preventDefault()でデフォルトのリンク処理をスキップして解決しました。

たまにポップアップの後に影が残ったりしますが、後日対応することにします。

マウスオーバーでぼかし画像表示
クリックで元の画像表示

<html lang="ja">
<HEAD>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="styles.css" />
<script type="text/javascript" src="function.js"></script>
<STYLE TYPE="text/css">
    .popup-image {
        position: absolute;
        top: 0;
        left: 300px;
        width: 300px;
        height: auto;
        display: none;
        filter: blur(10px); // ぼかし加工
      }
    .popup-image2 {
        position: absolute;
        top: 0;
        left: 300px;
        width: 500px;
        height: auto;
        display: none;
    }
</STYLE>
</HEAD>
<BODY bgcolor="#ffffff">

<p id ='1'><font size="2" color="#008080">1 : テスト<br></font>
<font size="2" color="#000080">2023/07/22(土) 10:23:46.76 ID:xxx<br></font>
<font size="2" color="#000B00"> テスト <br> <a href="画像のURL" 
rel="noopener noreferrer" onmouseover="showImage(this,event);" onmouseout="hideImage();" 
onclick="showImage2(this,event);">画像のURL</a>
</font></p>

<script>
    // ぼかしあり画像表示
    function showImage(element,event) {
        var imageSrc = element.getAttribute("href");
        var imageElement = document.createElement("img");
        imageElement.src = imageSrc;
        imageElement.classList.add("popup-image");
        document.body.appendChild(imageElement);
        imageElement.style.display = "block";

        var rect = imageElement.getBoundingClientRect();
        var y1 = rect.top;
        console.log("Popup y1 = " + y1);

        var y2 = event.clientY;
        console.log("Popup y2 = " + y2);

        var y = -y1 + y2 -100;
        if (y < -y1){
            y = -y1;
        }
        console.log("Popup y = " + y);

        imageElement.style.top = y;
    }

    // ぼかしなし画像表示
    function showImage2(element,event) {
        event.preventDefault();

        var imageSrc = element.getAttribute("href");
        var imageElement = document.createElement("img");
        imageElement.src = imageSrc;
        imageElement.classList.add("popup-image2");
        document.body.appendChild(imageElement);
        imageElement.style.display = "block";

        var rect = imageElement.getBoundingClientRect();
        var y1 = rect.top;
        console.log("Popup y1 = " + y1);

        var y2 = event.clientY;
        console.log("Popup y2 = " + y2);

        var y = -y1 + y2 -100;
        if (y < -y1){
            y = -y1;
        }
        console.log("Popup y = " + y);

        imageElement.style.top = y;
    }
    
    // 画像消去
    function hideImage() {
        var popupImages = document.getElementsByClassName("popup-image");
        while (popupImages.length > 0) {
            var image = popupImages[0];
            image.parentNode.removeChild(image);
        }

        var popupImages2 = document.getElementsByClassName("popup-image2");
        while (popupImages2.length > 0) {
            var image2 = popupImages2[0];
            image2.parentNode.removeChild(image2);
        }
    }
</script>
  
</BODY></html>

[C++] 340 BBS閲覧アプリの製作 その26 モーダルダイアログ設定 / Fl_Button修正 セル灰色トラブル対策 FLTK

[M1 Mac, MacOS Ventura 13.3.1, clang 14.0.3]

スレッドタイトルを選択して右クリックするとdatファイルを削除できますが、一応モーダルダイアログで確認するようにしました。

また右クリック時にセルがランダムに灰色になってしまう問題については、元ソースコードのFl_Button::draw関数に原因があることが判明しましたのでこれを修正しました。

具体的にはFl_Buttonにvalue関数でsetした値が1の場合はselection_color()である灰色になっていました。これをcolor()に変更しています。

作業効率を上げるためなのでしょうが、変数名が単純すぎるのも第三者から見ると読みにくくて仕方ないです。ただでさえC++の表現は記号が多くて抽象的なので、気を付けたいところです。

#include "MyButton.h"

MyButton::MyButton(int x, int y, int width, int height, const char *L)
 : Fl_Button(x, y, width, height, L) 
{
}

void MyButton::draw() {
    if (type() == FL_HIDDEN_BUTTON) return;
    Fl_Color col = value() ? color() : color();
    //   Fl_Color col = value() ? selection_color() : color(); // この行を修正
    draw_box(value() ? (down_box()?down_box():fl_down(box())) : box(), col);
    draw_backdrop();
    if (labeltype() == FL_NORMAL_LABEL && value()) {
        Fl_Color c = labelcolor();
        labelcolor(fl_contrast(c, col));
        draw_label();
        labelcolor(c);
    } else {
        draw_label();
    }
    if (Fl::focus() == this) draw_focus();
}

int MyButton::handle(int event) {
    switch (event) {
        case FL_ENTER :{ // マウスオーバー
            color(fl_rgb_color(160,216,239));
            redraw();
            return 1;
        }
        case FL_LEAVE :{ // マウスが離れる
            color(FL_WHITE);
            redraw();
            return 1;
        }
        case FL_PUSH:{ // マウスボタンを押す
            color(fl_rgb_color(0,255,255));
            redraw();
            return 1;
        }
        case FL_RELEASE :{ // マウスボタンを放す
            do_callback(FL_REASON_RELEASED);
            color(fl_rgb_color(FL_WHITE));
            redraw();
            return 1;
        }
        case FL_MOUSEWHEEL :
        case FL_FOCUS :
        case FL_UNFOCUS :
        case FL_KEYBOARD :
        case FL_SHORTCUT :
        case FL_DRAG :{
            return Fl_Button::handle(event);
        }
        default:{
            return Fl_Button::handle(event);
        }
    }
}

[C++] 339 BBS閲覧アプリの製作 その25 ホバー色設定 / 右クリックでdatファイル削除 FLTK

[M1 Mac, MacOS Ventura 13.3.1, clang 14.0.3]

アプリに2つの機能を追加しました。

1.スレッド一覧にホバーした際、色が変わる。左クリックするとさらに色が変わる。マウスボタンを放すと元の色に戻る。

2.スレッドタイトルを右クリックするとdatファイルを削除する。

#include "MyButton.h"

MyButton::MyButton(int x, int y, int width, int height, const char *L) : Fl_Button(x, y, width, height, L) 
{
}

int MyButton::handle(int event) {
    switch (event) {
        case FL_ENTER :{ // マウスオーバー
            color(fl_rgb_color(160,216,239));
            redraw();
            return 1;
        }
        case FL_LEAVE :{ // マウスが離れる
            color(FL_WHITE);
            redraw();
            return 1;
        }
        case FL_PUSH:{ // マウスボタンを押す 
            color(fl_rgb_color(0,255,255));
            redraw();
            break; 
            // return 1にするとセルが灰色になり元に戻らない
            // breakはFl_Buttonを上書き?、return 1は追記?
        }
        case FL_RELEASE :{ // マウスボタンを放す
            do_callback(FL_REASON_RELEASED);
            color(fl_rgb_color(0,255,255));
            redraw();
            return 1;
        }
        default:
            return Fl_Button::handle(event);
    }
}
// 右ボタンクリックの場合はdatファイルを削除
    int buttonType = Fl::event_button();
    cout << "buttonType: " << buttonType << endl;
    
    if (buttonType == 3){ // マウス左ボタン:1,中央:2,右:3
        std::ifstream datFile(filename);

        if (datFile.is_open()) {
             if (std::remove(filename.c_str()) != 0) {
                cout << "datファイルの削除に失敗しました。" << endl;
            } else {
                cout << "datファイルが正常に削除されました。" << endl;
            }
            return;
        }
        return;
    }

[C++] 338 BBS閲覧アプリの製作 その24 GUIに変数が表示されない場合 / DL済レス数表示および自動スクロール JavaScript

[M1 Mac, MacOS Ventura 13.3.1, clang 14.0.3]

これまではnew/deleteやmalloc/freeで変数をメモリに格納して解放するまで保持できるようにしていましたが、パブリック変数にするだけで同様の効果が得られることに今さら気が付きました。

変数名が分かってしまうとデータ改変の可能性が高まりますが、自分用のアプリだったら問題ないでしょう。まあ、スコープをむやみに広げるのは良くないですが。

リロード時刻をGUIに表示できず、少々手間取っているうちに他ソースファイルでの使用不使用に係わらずパブリック変数にしてしまえばいいという手抜きに気付いたのでした。

追加機能ですが、DL済レス数を表示できるようにしました。次にDATファイルをダウンロードして表示する際は前回ダウンロードした最後のレスへ自動スクロールします。

<script>
var element = document.getElementById(305);
var rect = element.getBoundingClientRect();
var y = rect.top;
console.log("toOldRes y = " + y);
scrollTo(0, y);
</script>

[C++] 337 BBS閲覧アプリの製作 その23 ポップアップ位置の改善 JavaScript

[M1 Mac, MacOS Ventura 13.3.1, clang 14.0.3]

レスアンカーのポップアップの位置をFl_WebViewの先頭やや下に固定していましたが、視線の動きを減らすため、ホバーした位置の近くに変更しました。

正確にはホバー位置を基準に100px上方へずらしています。上にはみ出さないよう調整しています。

ところでアプリのサイズは今のところ2.7MBになります。curlやxml2ライブラリは含みません。かなり軽量でかつ高速です。

単機能Electronアプリ(JavaScript)210MBやJavaアプリを除くと、これまで作ってきたデスクトップアプリでは最大サイズになりました。ちなみにSwiftで作ったメモアプリiOS版、ChatGPTアプリiOS版はそれぞれ4.2MB、2.7MBです。

function showPopup(id, event) {
    var element = document.getElementById(id);
    element.style.visibility = (element.style.visibility == 'visible')? "hidden": "visible";

    var rect = element.getBoundingClientRect();
    var y1 = rect.top;
    console.log("Popup y1 = " + y1);

    var y2 = event.clientY;
    console.log("Popup y2 = " + y2);

    var y = -y1 + y2 -100;
    if (y < -y1){
        y = -y1;
    }
    console.log("Popup y = " + y);

    element.style.top = y;
}
<p id="251" class="tips"><font size="2" color="#008080">251 : 投稿者名
<br></font><font size="2" color="#000080">2023/07/23(日) 19:36:50.61 ID:xxx<br></font>
<font size="2" color="#000B00"> <span onmouseover = "showPopup(230,event);" onmouseout = "showPopup(230,event);" >
<a href="../xxx/230" rel="noopener noreferrer" target="_blank">>>230</a></span>
<br> 本文 </font></p>

[C++] 336 BBS閲覧アプリの製作 その22 Fl_WebViewにコンソール代替機能実装 JavaScript

[M1 Mac, MacOS Ventura 13.3.1, clang 14.0.3]

Fl_WebViewはGoogle ChromeのようなWebブラウザに近い機能を持っていますが、さすがに検証機能はありません。

そこでコンソールのような機能を持たせるため、テスト用のポップアップ要素をあらかじめ仕込んでおき、必要時にテスト関数でポップアップに結果を出力させるようにしました。

下図の例では、GUI右下のスクロールボタンをホバーした時にテスト関数が動作し、その結果を右上のポップアップに出力しています。

この機能により、Fl_WebViewは最新のJavaScriptであるES2022に対応していることが分かりました。

普段はChromeの検証機能を使えばいいと思いますが、Fl_WebView固有の性質を調べる場合は今回の機能を使用します。

function test(){
    const msg = "This browser supports ES2022.";
    const msg2 = "This browser don't supports ES2022.";

    var element = document.getElementById("test");
    element.style.visibility = (element.style.visibility == 'visible')? "hidden": "visible";
    var rect = element.getBoundingClientRect();
    var y = rect.top;
    console.log("Popup y = " + y);
    element.style.top = -y + 20;

    try {
        eval('class Foo { #bar = 42; }');
        element.textContent = msg;
    } catch (error) {
        element.textContent = msg2;
    }
}

[C++] 335 BBS閲覧アプリの製作 その21 リロード機能実装 

[M1 Mac, MacOS Ventura 13.3.1, clang 14.0.3]

アプリにスレッド一覧とスレッドのリロード機能を実装しました。

これで製作前に使用していた既存BBSアプリに抱いていたUIへの不満点をほとんど解消した形になりました。黒地に赤字の見にくい配色、リロードボタンの不可解な位置、最上部・最下部へのスクロールボタンの小ささなどUI開発者の端くれとして看過できませんでした。過疎っているmacOSアプリ界隈ですから、それでも寡占状態になっています。

JavaScriptのポテンシャルを生かして画像表示などさらに機能を増やしていくつもりです。

[C++] 334 BBS閲覧アプリの製作 その20 スクロールボタン配置 JavaScript

[M1 Mac, MacOS Ventura 13.3.1, clang 14.0.3]

HTML右端にスクロールボタンを配置し、最上部、最下部へ移動できるようにしました。ボタンはスクロールしても動きません。

Fl_Buttonで移動させたかったのですが、Fl_WebViewの子要素としてスクロールバーを把握する方法が分からないため、この手法にしました。

ここ数日結構根を詰めて取り組んだので、しばらくのんびりします。

振り返ればプログラミングを始めて3年半が経ちました。進度としてはまずまずだと思います。

    <html lang="ja">
    <HEAD>
    <meta charset="utf-8">
    <STYLE TYPE="text/css">
    A:link {    color: #0000ff;
                text-decoration: none; }
    A:visited { color: #008080;
                text-decoration: none; }
    A:hover {   color: #ff0000;
                text-decoration: underline; }
    body {
        font-family:"Helvetica","ヒラギノ角ゴ";
        margin-left: 20px;
    }

    .tips {
        position: absolute;
        top: 0px;
        left: 100px;
        visibility:hidden;
        background-color: #E0FFFF;
        margin-left:2%;
        padding:1em;
    }

    .rightTop-fixed-button {
        position: fixed;
        top: 0;
        right: 0;
        width: 50px;
        height: 20px;
        /* padding-top: 0px; */
        text-align: center;
        background: #FFFFFF;
        border-top: 4px solid #fff;
    }

    .rightTop-fixed-button button {
        font-size: 16px;
        cursor: pointer;
        vertical-align: middle;
    }

    .rightTop-fixed-button button:hover {
        opacity: 0.2;
    }

    .rightBtm-fixed-button {
        position: fixed;
        bottom: 10px;
        right: 0;
        width: 50px;
        height: 20px;
        /* padding-top: 0px; */
        text-align: center;
        background: #FFFFFF;
        border-top: 4px solid #fff;
    }

    .rightBtm-fixed-button button {
        font-size: 16px;
        cursor: pointer;
        vertical-align: middle;
    }

    .rightBtm-fixed-button button:hover {
        opacity: 0.2;
    }

    </STYLE>
    <script>
        function showPopup(id) {
            if(document.getElementById){
                var element = document.getElementById(id);
                element.style.visibility = (element.style.visibility == 'visible')? "hidden": "visible";

                var rect = element.getBoundingClientRect();
                var y = rect.top;
                console.log("Popup y = " + y);

                element.style.top = -y + 20;
            }
        }

        function moveTop(){
            scrollTo(0,0);
        }

        function moveBottom(){
            let scrollHeight = Math.max(
            document.body.scrollHeight, document.documentElement.scrollHeight,
            document.body.offsetHeight, document.documentElement.offsetHeight,
            document.body.clientHeight)

            scrollTo(0, scrollHeight);
        }
    </script>
    
    
    </HEAD>
    <BODY bgcolor="#ffffff">

    <div class="rightTop-fixed-button">
    <button onclick="moveTop();">|<</button>
    </div>
    <div class="rightBtm-fixed-button">
    <button onclick="moveBottom();">>|</button>
    </div>

<以下略>