実を言うとTab Groupsはもうだめです。

f:id:dNaga392:20170130224504p:plain

今朝 Firefox を開くと、Tab Groups からの last notify のページが開きました。

記事によると、これまでアドオンとして提供されていたTab Groupsの開発が中止されるとのことでした。

タブグループ機能がFirefox本体から廃止され、その代わりになったのがこのTab Groupsで、 これまではQuicksaver氏が開発を続けてきました。

Quicksaver氏のTab Groupsは、軽快で安定した動作の高品質なアドオンで、 私のインターネットライフをより良いものとしてくれました。

しかしながら、Mozillaの示したFirefoxの今後の方針は、 アドオン開発の継続に大きな負荷をかけるものでした。

Quicksaver氏によると、いくらか抵抗し意見を交わしたものの Mozzilaの方針は変わることはなかったそうです。

そのため、開発を続けることに限界を感じたQuicksaver氏は Tab Groupsを始め、彼のアドオンの開発をすべて終えることにしたということです。

愛用していたTab Groupsの開発終了は大変残念ですが、 これをきっかけにタブの整理を進めてみるのもいいかもしれません。

これまで高品質なアドオンを提供してくれたQuicksaver氏に深い感謝を。


追記

forest.watch.impress.co.jp

そこで従来型アドオンは、順次“WebExtensions”ベースのアドオンへと置き換えられる。具体的には、4月に「Firefox 53」がリリースされるタイミングで“addons.mozilla.org(AMO)”における従来型アドオンの受け付けが終了となり、11月の「Firefox 57」以降は従来型アドオンがサポートされなくなる。

11月がリミットっぽい

【Git】 fatal: not under version control, source=A.txt, destination=B.txt

Gitで表題のようなエラーが出た。

ファイル名をチェックすると、ファイル名の一部が大文字小文字で異なっていた。 これは git を介さずにファイル名を変更したためとのこと[1]。

解決法には以下の2つがある。

  • 元のファイル名に戻す。
  • ファイル名変更をgitに伝える。

ここでは後者の方法を提示する。 手順は次の通りである。

  1. 元のファイル名でgit mv コマンドを実行する。
> ls
a.txt
> mv a.txt A.txt
> ls
A.txt
> git mv A.txt B.txt
fatal: not under version control, source=A.txt, destination=B.txt
> ls
A.txt
> git mv a.txt B.txt
> ls
B.txt

あるいは、ファイル名自体を変更する方法もあるが、手間なのでお勧めはしない。

> ls
a.txt
> mv a.txt A.txt
> ls
A.txt
>ls
A.txt
> mkdir dirB
> git mv *.txt dirB
fatal: not under version control, source=A.txt, destination=dirB/A.txt
> mv A.txt _A.txt
>ls
_A.txt
> git add .
> git commit -m "rename temp filename"
> mv _A.txt A.txt
>ls
A.txt
> git add .
> git commit -m "rename true filename"
> git mv *.txt dirB
> cd dirB
> ls
A.txt

参考資料

[1] rcmdnk.github.io

【Qt】ウィジェットのスクリーンショット

メインウィンドウをスクリーンショットする

メインウィンドウのスクリーンショットを撮る場合、 QPixmap::grabWidget を利用する。

    // メインウィンドウのスクリーンショット
    pixmap = QPixmap::grabWidget( this );
    // クリップボードにビットマップをコピー
    QClipboard * clipboad = qApp->clipboard();
    clipboad->setPixmap( pixmap );

ただし、この場合ウィンドウフレームは含まれない。 これはウィンドウフレームがQtの処理範囲外のためである。

そのため、ウィンドウフレームを含める場合は、 より大きい範囲、スクリーンのスクリーンショットを撮ってトリミングをする。

    QDesktopWidget dw;
    QWidget * screen=dw.screen( dw.screenNumber(this) );
    QRect rect = geometry();
    WId wid = screen->winId();
    // タイトルバーの高さを取得
    QStyleOptionTitleBar options;
    options.initFrom(this);
    int tbh = this->style()->pixelMetric( QStyle::PM_TitleBarHeight, &options, this ) - 4;
    // QPixmap::grabWindow(WId,int,int,int,int) の実行に必要なアトリビュートを設定
    setAttribute( Qt::WA_TranslucentBackground );
    // ウィンドウフレーム描画の数値を設定
    // 以下の6変数はWin7の実測値のため、実行環境の影響を受ける
    const int fw = 8; ///< frame width
    const int fh = 5; ///< frame height
    const int cx = rect.x() - fw;                  ///< caption x
    const int cy = rect.y() - (tbh + fh - 3);      ///< caption y
    const int cw = rect.width() + fw * 2;          ///< caption width
    const int ch = rect.height() + (tbh + fh * 2); ///< caption height
    pixmap = QPixmap::grabWindow( wid, cx, cy, cw, ch );

    // スクリーン外の非表示部分も描画するため、
    // ウィジェット部分を再描画する。
    QPixmap pix_wdt = QPixmap::grabWidget( this );
    QPainter painter( &pixmap );
    painter.drawPixmap( fw, (tbh + fh - 3), pix_wdt );
    // クリップボードにビットマップをコピー
    QClipboard * clipboad = qApp->clipboard();
    clipboad->setPixmap( pixmap );

コメントに記載したようにリテラル整数は実測値のため、 環境によっては意図する結果が得られない場合がある。

MDIのサブウィンドウをスクリーンショットする

MDIサブウィンドウの場合、ウィンドウフレームもQtの処理範囲に含まれる。 そのため、QMdiSubWindow を QPixmap::grebWidget することでフレーム付きスクショが撮れる。

    // ウィンドウフレームを含むスクリーンショット
    QMdiSubWindow * activeWindow = mdiArea->activeSubWindow();
    if ( activeWindow == NULL)
    {
        return false;
    }
    Q_ASSERT( activeWindow );
    pixmap = QPixmap::grabWidget( activeWindow );
    // クリップボードにビットマップをコピー
    QClipboard * clipboad = qApp->clipboard();
    clipboad->setPixmap( pixmap );

ウィンドウフレームなしのスクリーンショットの場合、 QMdiSubWindow の widget を QPixmap::grebWidget することで得られる。

    // ウィンドウフレームを含まないスクリーンショット
    QMdiSubWindow * activeWindow = mdiArea->activeSubWindow();
    if ( activeWindow == NULL)
    {
        return false;
    }
    Q_ASSERT( activeWindow );
    pixmap = QPixmap::grabWidget( activeWindow->widget() );
    // クリップボードにビットマップをコピー
    QClipboard * clipboad = qApp->clipboard();
    clipboad->setPixmap( pixmap );

参考資料

【Qt】Sleep()の実装例

QtによるSleepメソッド実装方法を記します(ほぼ参考資料の転載です)。

Sleep は指定時間の間、処理の実行を中断するメソッドです。 実装により次の2種類が存在しています。

  • 秒指定
  • ミリ秒指定

前者には C++標準ライブラリ のSleepメソッドが、 後者には Windowライブラリ のSleepメソッドがあります。

本記事ではこの2種類を対象に実装例を示します。

Sleep(秒指定)

#include <QtCore/QThread>

void Sleep( unsigned long secs )
{
    QThread::sleep( secs );
}

Sleep(ミリ秒指定)

#include <QtCore/QEventLoop>
#include <QtCore/QTimer>

void Sleep( int msec )
{
    QEventLoop loop;
    QTimer::singleshot( msec, &loop, SLOT( quit() ) );
    loop.exec();
}

参考資料

以上

Slack への Hubot 導入はじめ

導入でつまずいたので、記事で残します。

環境とインストールしておくツール

git は必須ではないですが、git bashから構築及び実行をしているため挙げました。 git と node.js については公式配布物から最新版をインストールでよいです。 当該環境ではインストーラによる導入をしています。

Hubot を召喚しよう

Hubot プロジェクトの作成には、公式ドキュメントが大きな力になります。

https://hubot.github.com/docs/

召喚例として導入時の内容を以下に記述します。 環境を整えたら、まずは git bash を起動して node および npm が使えることを確認します。

$ node -v
v5.2.0
$ npm -v
3.5.2

npm のバージョンが確認できたら、Hubotのジェネレータをインストールします。

$ npm install -g yo generator-hubot

ここまでできたら、いよいよ召喚の議を行います。 召喚先のプロジェクトフォルダを作成し、Hubotをインストールします。

$ mkdir myhubot
$ cd myhubot
$ yo hubot

これでhubotが降臨します。 降臨中に Owner, Bot name, Description, Bot adapter の4つを聞かれるので回答します。

前者3つは自由回答でかまいませんが、adapter は slack と回答しましょう。 そうすることで slack 向けの初期設定がされます。

降臨後はコマンドで ./bin/hubot を実行し、対話を試みます。 対話では Bot name で決めた名前でよびかけます。 以下は Bot name を myhubot とした場合の対話例です。

$ bin/hubot
myhubot ping
myhubot> PONG

myhubot が PONG を返したので対話成功です。 インストールできたと言えます。

各コマンド実行の際に yeoman が不足しているなどの警告があれば適時インストールしてください。 自分の場合、(タイミングは忘れましたが)yeoman-doctor が欲しいと言われたので npm でインストールしました。

$ npm -g install yeoman-doctor

Hubot を Slackに呼ぼう

Slack に呼ぶ方法は以下を参考にしました。

Slack の Costom IntegrationページでHubotを召還する準備をします。 準備は2つです。

  1. Hubotを降ろすBotアカウントを追加
  2. API Token を控える

Botアカウントを追加できたら、Slackでの対話をしましょう。 以下を実行して、Hubotを起こします。 xxxxxxxxxxxxxxx には、先ほどのAPI Token を置換して実行ください。

$ HUBOT_SLACK_TOKEN=xxxxxxxxxxxxxxx bin/hubot --adapter slack

無事実行できたら、Hubotを降ろしたアカウント宛に ping します。 PONG をリプライされれば対話成功です。

導入はじめの手続きは以上です。

今は script ディレクトリに 対話例のスクリプト example.coffee があるので、いろいろと試して遊んでこの後を考えてます。 また、実際に運用する場合はローカルではなく heroku などを使うことになるかと思います(が、まだ試していません)。

参考

【QtTest】 テストプロジェクトでテストデータにコンテナクラス(QList、QMap、...)を使う

Qtはテストプロジェクト向けパッケージでテストデータを受け渡す方法として、 次の方法が提供されている。

標準組み込み型およびQtライブラリ提供クラスの多くは、 addColumn メソッドで登録したテストデータを、QFETCH マクロで受け取ることができる。

// テストデータ登録メソッド
void TestMyClass::function_data()
{
QTest::addColumn< int >( "number" );
QTest::addColumn< QString >( "text" );

// newRow...
}

// テスト実行メソッド
void TestMyClass::function()
{
QFETCH(int, number);
QFETCH(QString, text);

// QCOMPARE...
}

同様の記述でコンテナクラスをテストデータとする場合、 コンパイルエラーを起こすため、テストデータとして利用できない。

// コンパイルエラーを起こす記述
// テストデータ登録メソッド
void TestMyClass::function_data()
{
QTest::addColumn< QMap< QString, QString > >( "map_data" );

// ...
}

// テスト実行メソッド
void TestMyClass::function()
{
QFETCH(QMap< QString, QString >, map_data);

// ...
}

そのため、コンテナクラスをテストデータとする場合、 次の2つを利用する。

  • typedef文
  • Q_DECLARE_METATYPE マクロ
// 非コンテナのシノニムを定義
typedef QMap< QString, QString > QStringMap;
// QMetaType に登録
Q_DECLEARE_METATYPE( QStringMap );
// テストデータ登録メソッド
void TestMyClass::function_data()
{
QTest::addColumn< QStringMap >( "map_data" );

// ...
}

// テスト実行メソッド
void TestMyClass::function()
{
QFETCH(QStringMap, map_data);

// ...
}

参考資料

【Qt】View-QStandardItemModel 導入リンク集

ブックマークが溜まってきたので、内容をまとめる前に一度リンクを記事にしました。 まとまりはないので、たどり着いてしまった人は自分の目で確認してくだし。。