以前にもstd::filesystemでディレクトリツリーを作成するコードをいつもより少しだけ真面目に書いた。
#include <iostream> #include <vector> #include <string> #include <filesystem> // SetConsoleOutputCP のために必要 #include <windows.h> enum class FileType { Ignore, Directory, File }; enum class ExceptionType { Success, // 子要素の収集に失敗(イテレータ取得に失敗) // あるノードにこのエラーがついていた場合、そのノードの子要素は取得できていない ChildrenIteratorError, NodeTypeError, NodeNameError, NotInit, Other, }; using PathString = std::u8string; inline PathString ToFilenameString(const std::filesystem::path& path) { std::filesystem::path tmp; if (path.filename().empty()) { tmp = path.parent_path(); // 末尾がスラッシュなら親ディレクトリを取得 } else { tmp = path; // 末尾がスラッシュでないならそのまま } PathString fname = tmp.filename().u8string(); return fname; } struct ErrorData { ExceptionType type; std::string message; std::filesystem::path path; public: ErrorData() : type(ExceptionType::Success) {} ErrorData(ExceptionType type, const std::string& message) : type(type), message(message) {} };
// 自作の例外クラス class FileSystemTreeException : public std::exception { private: std::string message; ErrorData error_data; public: explicit FileSystemTreeException(const std::string& msg, const std::filesystem::path& _path_, const ExceptionType type) : message(msg) { error_data.type = type; error_data.message = msg; error_data.path = _path_; } const char* what() const noexcept override { return message.c_str(); } const ErrorData& getErrorData() const { return error_data; } };
// ノードの子ノード配列を扱うクラス
// FileSystemTreeで子ノードの読み込みに失敗した場合はerror_dataに内容が入っている
class ChildrenNodes { std::vector<std::unique_ptr<class FileSystemTree>> children; ErrorData error_data; public: ChildrenNodes() {} ChildrenNodes& operator=(std::vector<std::unique_ptr<class FileSystemTree>>&& other) noexcept { children = std::move(other); return *this; } void push_back(std::unique_ptr<class FileSystemTree>&& node) { children.push_back(std::move(node)); } bool isValid()const { return error_data.type == ExceptionType::Success; } void setError(const ErrorData& err) { error_data = err; } const ErrorData& getError() const { return error_data; } std::vector<std::unique_ptr<class FileSystemTree>>::const_iterator cbegin() const { return children.cbegin(); } std::vector<std::unique_ptr<class FileSystemTree>>::const_iterator cend() const { return children.cend(); } std::vector<std::unique_ptr<class FileSystemTree>>::iterator begin() { return children.begin(); } std::vector<std::unique_ptr<class FileSystemTree>>::iterator end() { return children.end(); } std::vector<std::unique_ptr<class FileSystemTree>>::const_iterator begin() const { return children.cbegin(); } std::vector<std::unique_ptr<class FileSystemTree>>::const_iterator end() const { return children.cend(); } };
// ディレクトリツリーのクラス
class FileSystemTree { PathString name; // ファイル名・ディレクトリ名 FileType type; // ファイルかディレクトリか ErrorData error_data; // このノードのエラー情報 ChildrenNodes children; // 子ノード一覧 ChildrenNodes error_children; // エラーが発生した子ノード一覧 public: FileSystemTree(const PathString& name, const FileType type) : name(name), type(type) {} FileSystemTree() :type(FileType::Ignore) {}; FileSystemTree(const ErrorData& err) : error_data(err), type(FileType::Ignore) {}
// このノードについて、子ノードの読み込みに失敗した場合に設定 // 注意:子ノード一つ一つではなく、イテレータの取得の段階で失敗したような場合に使用 void setChildrenError(const ExceptionType type, const std::string str) { children.setError(ErrorData(type, str)); }
// このノードの子ノード一覧を設定
// アクセスできない子を見つけた場合はerrorchildrenに入れて渡す
void setChildren( std::vector<std::unique_ptr<FileSystemTree>>&& children, std::vector<std::unique_ptr<FileSystemTree>>&& errorchildren) { this->children = std::move(children); this->error_children = std::move(errorchildren); } const ChildrenNodes& getChildren()const { return children; }// 子ノード一覧へアクセス void setError(const ErrorData& err) { error_data = err; }// このノードがエラーノードであることを設定(普通コンストラクタで指定可能なので使わない) bool isChildrenValid() const { return children.isValid(); }// 子ノード一覧の生成に成功したかどうか。たとえ要素0でも生成に成功していればtrue ChildrenNodes& getErrorChildren() { return error_children; }// 子ノード一一覧生成時、読み込めなかったときに発生したエラー一覧 const PathString& getName() const { return name; }// ノード名 FileType getType() const { return type; }// ファイルかディレクトリか bool isFailure() const { return error_data.type != ExceptionType::Success; }// このノードがエラーノードであるか const ErrorData& getError() const { return error_data; }// このノードのエラー情報 };
std::unique_ptr<FileSystemTree> MakeFilesystemNode(const std::filesystem::path& path) { FileType type; PathString name; try { // ノードの種類を特定 type = std::filesystem::is_directory(path) ? FileType::Directory : FileType::File; } catch (const std::exception& e) { throw FileSystemTreeException(e.what(), path, ExceptionType::NodeTypeError); } try { // ノードの名前を取得 name = ToFilenameString(path); } catch (const std::exception& e) { throw FileSystemTreeException(e.what(), path, ExceptionType::NodeNameError); } std::unique_ptr<FileSystemTree> the_node = std::make_unique<FileSystemTree>(name, type); return the_node; }
// ディレクトリツリーを構築する関数 void buildDirTreeCore(std::unique_ptr<FileSystemTree>& this_directory, const std::filesystem::path& path) { if (this_directory->getType() == FileType::Directory) { // ノードがディレクトリの場合、子ノードを追加 // pathの中身を取得するイテレータを作成 std::filesystem::directory_iterator dir_itr; try { dir_itr = std::filesystem::directory_iterator(path); } catch (const std::filesystem::filesystem_error& e) { // 失敗した場合、このディレクトリ内のファイルを取得できないことを意味する this_directory->setChildrenError(ExceptionType::ChildrenIteratorError, e.what()); return; } catch (const std::exception& e) { this_directory->setChildrenError(ExceptionType::Other, e.what()); return; } std::vector<std::unique_ptr<FileSystemTree>> nowchildren; // this_directoryの子ノード一覧 std::vector<std::unique_ptr<FileSystemTree>> errors; // エラーが発生した子ノード一覧 // ディレクトリ内の項目を取得して追加 for (const auto& entry : dir_itr) { std::unique_ptr<FileSystemTree> newnode; try { // 子要素のリストを作成 newnode = MakeFilesystemNode(entry.path()); nowchildren.emplace_back(std::move(newnode)); } catch (const FileSystemTreeException& e) { // エラーが発生した場合、エラーノードを追加 errors.push_back(std::make_unique<FileSystemTree>(e.getErrorData())); } } for (auto& subdirectory : nowchildren) { // ディレクトリのみ、サブディレクトリを再帰的に処理 if (subdirectory->getType() == FileType::Directory) { const std::filesystem::path& subpath = path / subdirectory->getName(); buildDirTreeCore(subdirectory, subpath); } } this_directory->setChildren(std::move(nowchildren), std::move(errors)); } }
std::unique_ptr<FileSystemTree> BuildTree(const std::filesystem::path& path) { std::unique_ptr<FileSystemTree> this_directory; auto entry = path.lexically_normal(); this_directory = MakeFilesystemNode(entry); // パスからノードを作成 buildDirTreeCore(this_directory, entry); return this_directory; }
///////////////////////////////////////////////////////////////////////////////////// // ファイルツリーを表示 void PrintTree(const std::unique_ptr<FileSystemTree>& node, int depth = 0) { auto to_string = [](const FileType type) { switch (type) { case FileType::Ignore: return "*"; case FileType::Directory: return "[DIR] "; case FileType::File: return "[FILE] "; } return "Unknown"; }; std::string indent(depth * 2, ' '); if (node->isFailure()) { std::cout << indent << to_string(node->getType()) << (const char*)node->getName().c_str() << " : " << node->getError().message << "\n"; } else { std::cout << indent << to_string(node->getType()) << (const char*)node->getName().c_str() << "\n"; } if (node->isChildrenValid() == false) { std::cout << indent << " ** Children Error: " << node->getChildren().getError().message << "\n"; } else { for (const auto& child : node->getChildren()) { PrintTree(child, depth + 1); } } for (const auto& error : node->getErrorChildren()) { std::cout << indent << " ** Error: " << error->getError().message << "\n"; } } int main() { // ロケール設定 std::locale::global(std::locale("ja_JP.UTF-8")); std::cerr.imbue(std::locale("ja_JP.UTF-8")); std::cout.imbue(std::locale("ja_JP.UTF-8")); SetConsoleOutputCP(CP_UTF8); std::string path = R"(C:\test\myaccess)"; std::unique_ptr<FileSystemTree> root = BuildTree(path);// ファイルパスからツリーを作成 PrintTree(root); }
以前、スレッドプールのサンプルのリンクを張った。それを加工したものを作成。
#include <deque> #include <vector> #include <thread> #include <mutex> #include <condition_variable> #include <cassert>
//! @brief スレッドに行わせる仕事のインターフェース //! @details run()メソッドを実装し、処理内容を記述する \n //! 使用例: //! @code //! class MyWork : public JobBase { //! public: //! void run() override {} //! }; //! @endcode class JobBase { public: virtual void run() = 0; virtual ~JobBase() {} };
//! @brief ジョブをためるキュー class Queue { public: Queue() {} void push(std::unique_ptr<JobBase>&& data) { _deque.emplace_back(std::move(data)); } std::unique_ptr<JobBase> pop() { if (_deque.empty()) { return nullptr; } auto data = std::move(_deque.front()); _deque.pop_front(); return data; } bool empty() const { return _deque.empty(); } private: // ジョブがたまっているキュー // ThreadPoolで作成したN個のThreadWorkerスレッドが、このキューからジョブを取り出して実行する std::deque<std::unique_ptr<JobBase>> _deque; };
//! スレッドプールで動いているスレッドの実装 class ThreadWorker { public: ThreadWorker(bool& isTerminationRequested, Queue& queue, std::mutex& mutex, std::condition_variable& cv, int& activeJobCount, std::condition_variable& activeJobCv) : _isTerminationRequested(isTerminationRequested), _queue(queue), _mutex(mutex), _cv(cv), _activeJobCount(activeJobCount), _activeJobCv(activeJobCv) {} void operator()() { while (1) { std::unique_ptr<JobBase> jobinstance; { std::unique_lock<std::mutex> ul(_mutex); while (_queue.empty()) { if (_isTerminationRequested) { return; } _cv.wait(ul); } jobinstance = _queue.pop(); assert(jobinstance != nullptr); _activeJobCount++; } jobinstance->run(); // ジョブを実行 { std::unique_lock<std::mutex> ul(_mutex); _activeJobCount--; // 実行中ジョブ数を減らす if (_activeJobCount == 0 && _queue.empty()) { _activeJobCv.notify_all(); // すべてのジョブが完了したことを通知 } } } } private: bool& _isTerminationRequested; Queue& _queue; std::mutex& _mutex; std::condition_variable& _cv; int& _activeJobCount; // 実行中ジョブ数 std::condition_variable& _activeJobCv; // waitForCompletion() へ通知 };
//! @brief スレッドプールクラス //! @details N個のスレッドで、M個のジョブを実行する。\n //! waitForCompletion()で、現在キューを入れたすべてのジョブが完了するまで待機できる \n //! 使用例: //! @code //! ThreadPool pool(4); //! pool.add(std::make_unique<MyWork>()); //! pool.add(std::make_unique<MyWork>()); //! pool.waitForCompletion(); //! pool.add(std::make_unique<MyWork>()); //! pool.add(std::make_unique<MyWork>()); //! @endcode class ThreadPool { public: ThreadPool(int threadCount) : _isTerminationRequested(false), _activeJobCount(0) { for (int n = 0; n < threadCount; n++) { auto worker = std::make_shared<ThreadWorker>(_isTerminationRequested, _queue, _mutex, _cv, _activeJobCount, _activeJobCv); _workers.push_back(worker); _threads.emplace_back(std::thread(std::ref(*worker))); } } ~ThreadPool() { { std::unique_lock<std::mutex> ul(_mutex); _isTerminationRequested = true; } _cv.notify_all(); for (auto& thread : _threads) { thread.join(); } } //! @brief すべてのジョブが完了するまで待機する void waitForCompletion() { std::unique_lock<std::mutex> ul(_mutex); _activeJobCv.wait(ul, [this]() { return _activeJobCount == 0 && _queue.empty(); }); } //! @brief ジョブをキューに追加 //! @param jobinstance ジョブ void add(std::unique_ptr<JobBase>&& jobinstance) { { std::unique_lock<std::mutex> ul(_mutex); _queue.push(std::move(jobinstance)); } _cv.notify_all(); } private: bool _isTerminationRequested; Queue _queue; std::mutex _mutex; std::condition_variable _cv; std::vector<std::thread> _threads; std::vector<std::shared_ptr<ThreadWorker>> _workers; int _activeJobCount; // 実行中ジョブ数 std::condition_variable _activeJobCv; // ジョブの完了を待機するための条件変数 };
スレッドを待機させるためのcondition_variableの使い方を確認中。
とりあえず使い方や注意点をまとめたコードを書いてみたがなんか何をやりたいのかよくわからないコードになったのでもう少し考える。
#include <iostream> #include <vector> #include <thread> #include <mutex> #include <condition_variable> #include <random> #include <chrono> std::vector<double> data; std::mutex mtx; std::condition_variable cv; // データが10個以上あればメインスレッドを起こすという条件を定義 // 判断中にdata.size()が変化する可能性があるので、必ずロック中に呼び出すこと bool allow_to_main_wakeup() { return data.size() >= 10; }
// 無限にデータを生成し続ける関数 // ただし、10個データが貯まったらメインスレッドで処理する要求を出す // メインスレッドがデータを移動する間、このスレッドは休止する void Factory() { // 値を生成する関数 int count = 0; auto create_value = [&count]() { return count++; }; // 無限にデータを生成してdataに追加し続ける while (true) { bool allow_to_wakeup = false; { // 仮に、先にメインスレッドでlockした場合、このスレッドはlock(mtx); でブロック中となる // メインスレッド側ですぐにcv.wait()で待機開始するため、すぐにこのブロックは解除されることが期待できる std::unique_lock<std::mutex> lock(mtx); //================================== // データ作成処理本体 data.push_back( create_value() ); //================================== // メインスレッドで処理してよいかを判断。 // data.size()が別スレッドで変化しないために、ロック中に判断する allow_to_wakeup = allow_to_main_wakeup(); // 仮に、先にFactoryでlockした場合、メインスレッドはlock(mtx); でブロック中となっている // ここでスコープを抜けてunlockすることでメインスレッドがlockできるようになり、すぐにcv.wait()で待機開始 } // 現在メインスレッドはcv.wait()で待機中となっている。 // 特定の条件を満たしたら、メインスレッドで処理するためにメインスレッドを起こす処理をする if (allow_to_wakeup == true) { // notify_oneは同じcv.waitにより待機中のスレッドを一つだけ起こす。 cv.notify_one(); // notify_one()はこのスレッドには何もしないのでここはすぐに処理される // これ以降の処理はmainスレッドとの並列処理となる } // あまり速く動いても動作確認がしづらいのでスリープを入れる std::this_thread::sleep_for(std::chrono::milliseconds(100)); } }
int main() { // Factory関数を別スレッドで実行 // 以後、別スレッドでデータが自動生成され続ける std::thread producer(Factory); std::vector<double> tmp; // while (true) { { // ここでロック。ただしFactory関数側とどちらが先にロックするかは不定 std::unique_lock<std::mutex> lock(mtx); // waitすると内部でmtx.unlock()が呼ばれる // これによりFactory側でlock(mtx)ができるようになり、データの生成が可能になる // 同時に、このスレッドはFactory側でnotify_one()が呼ばれるまでここで待機 cv.wait(lock, [] { return allow_to_main_wakeup(); }); // cv.waitのラムダ式はSpurious Wakeup対策。 // 希にwait()中に何もしていないのにスレッドが起こされることがある // そのため、起こされたときに本当に起こされるべきかを関数により確認する // ここから先はcv.notify_one()により起こされてから実行される // これ以降の処理はFactory関数との並列処理となる。 // なおwait()を抜けた時点で、lockは再びlock状態となる //================================== // 別スレッドで作成されたデータを移動 tmp = std::move(data); //================================== } std::cout << "Generated numbers: "; for (double num : tmp) { std::cout << num << " "; } std::cout << std::endl; } producer.join(); return 0; }
以下のようにコンテナを作ったらpython 3.9のインストールに一手間かかったのでメモ。
なおapt update && apt upgrade はしてある。
まずそのまま
とすると
が出てくる。ubuntu:latestで導入すると最小構成で入るのでデフォルトのレポジトリに入っていなかったりするらしい。
update-alternativesは複数バージョンあるコマンドに対して、どのバージョンを使うかを指定する。
python --version
Python 3.9.21
pip --version
pip 25.0.1 from /usr/local/lib/python3.9/dist-packages/pip (python 3.9)
SkSurfaceの使い方がずっとわからなかった。SkSurfaces::Rasterでインスタンスを作成できる。
void Test() { // #include "skia/include/core/SkSurface.h" SkImageInfo imginfo = SkImageInfo::Make(400, 400, kBGRA_8888_SkColorType, kOpaque_SkAlphaType); sk_sp<SkSurface> surface = SkSurfaces::Raster(imginfo); SkCanvas* canvas = surface->getCanvas(); // 図形の描画 SkPaint paint; paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(5); paint.setColor(SK_ColorBLUE); canvas->clear(SK_ColorWHITE); canvas->drawCircle(200, 200, 100, paint); // PNG出力 SkPixmap pixmap; if (surface->peekPixels(&pixmap)) { SkFILEWStream stream("test_sksurface.png"); SkPngEncoder::Options options; options.fZLibLevel = 9; SkPngEncoder::Encode(&stream, pixmap, options); } }
ユーザー環境変数からPATHを編集しようとしたところ、「この環境変数は大きすぎます。このダイアログで設定できる値の長さは最大2047 文字です。」が発生し、パスを追加することも削除することもできなくなった。
1.PATHの中の記述を、他の変数(例えばPATH_ANACONDA)に分離し、PATHの文字数を2047以下にする。
2.PATHには%変数名%を入れておく
これでPATHの内容を維持しつつ文字数を減らせる。
マウスの右・左・ホイールで回転、移動、スケーリングする。
#pragma once #ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/dcgraph.h> #include <wx/dcbuffer.h> #include <wx/geometry.h> #include <cmath> #include <wx/gdicmn.h>
//! @brief 画像を回転させる //! @param transform 回転させる画像のアフィン変換行列 //! @param imageCoordPos 回転の中心となる画像座標系の座標 //! @param rad 回転角度(ラジアン) void RotateAroundPoint(wxAffineMatrix2D& transform, const wxPoint2DDouble& imageCoordPos, const double rad); //! @brief 画像をクライアント座標系で移動させる //! @param transform 移動させる画像のアフィン変換行列 //! @param diff 移動量(パネルのクライアント座標系) void TranslateInScreenSpace(wxAffineMatrix2D& transform, const wxPoint& diff); //! @brief クライアント座標を元画像の座標系に変換 //! @param clientPos クライアント座標 //! @param transform 画像のアフィン変換行列 //! @return 元画像の座標系の座標 wxPoint2DDouble ClientToImage(const wxPoint& clientPos, const wxAffineMatrix2D& transform); //! @brief 画像を拡大・縮小する //! @param transform 拡大・縮小する画像のアフィン変換行列 //! @param scalefactor 拡大・縮小率 //! @param clientpos 拡大・縮小の中心座標(クライアント座標系) //! @details clientposに表示されているピクセルが移動しないように拡大・縮小する //! @return なし void Zoom(wxAffineMatrix2D& transform, double scalefactor, const wxPoint clientpos); //! @brief ウィンドウサイズの変化から、拡大・縮小率を計算する //! @param oldSize リサイズ前のウィンドウサイズ //! @param newSize リサイズ後のウィンドウサイズ //! @return 拡大・縮小率 double CalcResizeScaleFactor(const wxSize& oldSize, const wxSize& newSize); //! @brief マウスドラッグによる拡大・縮小率を計算する //! @param oldpos ドラッグ開始時のマウス座標 //! @param newpos ドラッグ中のマウス座標 //! @return 拡大・縮小率 double CalcDragScaleFactor(const wxAffineMatrix2D& transform, const wxPoint& oldpos, const wxPoint& newpos, const int pixelratio); //! @brief ウィンドウサイズが変化したときに、現在画面中央に表示されているピクセルが、リサイズ後も中央に表示されるようにする //! @param oldSize リサイズ前のウィンドウサイズ //! @param newSize リサイズ後のウィンドウサイズ //! @return なし void ResizeKeepCenter(wxAffineMatrix2D& transform, const wxSize& oldSize, const wxSize& newSize); //! @brief 画像をパネルにフィットさせるためのアフィン変換行列を計算 //! @param transform アフィン変換行列 //! @param imageSize 元画像のサイズ //! @param panelSize パネルのサイズ //! @return なし void FitImage(wxAffineMatrix2D& transform, const wxSize& imageSize, const wxSize& panelSize); //! @brief 3点の座標から回転角を計算する //! @param a 1つ目の座標(始点) //! @param b 2つ目の座標(回転中心) //! @param c 3つ目の座標(終点) //! @details cを中心に、aからbへ回転した時の回転角を計算する //! @return 回転角(ラジアン) double CalculateAngle(const wxPoint& a, const wxPoint& b, const wxPoint& c);
//////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // マウスによる移動量を角度で計算するクラス class MouseDrag { bool _during; // ドラッグ中かどうか wxPoint _center; // 角度計算の中心座標 wxPoint _from; // 開始地点のマウス座標 wxPoint _to; // 現在のマウス座標 public: MouseDrag(const wxPoint center) : _during(false), _center(center) {} MouseDrag() : _during(false), _center(0, 0) {} //! @brief ドラッグの開始地点 wxPoint GetFrom()const { return _from; } //! @brief ドラッグの現在地点 wxPoint GetTo()const { return _to; } //! @brief ドラッグ開始 void Start(const wxPoint newPos) { _from = newPos; _during = true; } //! @brief ドラッグ終了 void Stop() { _during = false; } //! @brief 現在回転中かどうか bool During() { return _during; } //! @brief マウスの移動量を更新 void Update(const wxPoint& newPos) {_to = newPos;} //! @brief マウスの移動量から角度を計算 double Angle() const{return CalculateAngle(_to, _center, _from);} //! @brief マウスの移動量を取得 wxPoint Difference()const {return _to - _from;} };
class ImagePanel : public wxPanel { enum class DragMode { NONE, MOVE, ROTATE, ZOOM }; wxAffineMatrix2D _transform; //!< アフィン変換行列 wxAffineMatrix2D _transform_back; //!< 移動・回転前のアフィン変換行列のバックアップ wxSize _winsize_back; //!< ウィンドウサイズが変更されるたびに更新されるウィンドウサイズのバックアップ // ウィンドウサイズの変化に関しては、変更開始イベントを拾えないため、変化するたびに前回からの差分に基づいて計算する DragMode _drag_mode; //!< ドラッグモード MouseDrag _mov_dragging; //!< マウスドラッグ間の移動量を計算・管理 std::weak_ptr<wxBitmap> _imageBitmap;//!< 画像データ public: ImagePanel(wxWindow* parent); //! @brief イベントのバインド void EventBind(); private: //! @brief ペイントイベントハンドラ void OnPaint(wxPaintEvent& event); //! @brief イベントハンドラ(移動開始イベントにバインドする用) void OnEventMoveStart(wxMouseEvent& event); //! @brief イベントハンドラ(移動終了イベントにバインドする用) void OnEventMoveStop(wxMouseEvent& event); //! @brief イベントハンドラ(回転開始イベントにバインドする用) void OnEventRotateStart(wxMouseEvent& event); //! @brief イベントハンドラ(回転終了イベントにバインドする用) void OnEventRotateStop(wxMouseEvent& event); //! @brief イベントハンドラ(ドラッグでスケーリング開始イベントにバインドする用) void OnEventScaleStart(wxMouseEvent& event); //! @brief イベントハンドラ(ドラッグでスケーリング終了イベントにバインドする用) void OnEventScaleStop(wxMouseEvent& event); //! @brief マウス移動イベントハンドラ void OnMouseMove(wxMouseEvent& event); //! @brief イベントハンドラ(ホイール回転でスケーリングイベントにバインドする用) void OnEventScaleChange(wxMouseEvent& event); //! @brief ウィンドウサイズ変更イベントハンドラ void OnResize(wxSizeEvent& event); public: //! @brief 表示する画像をセット //! @param bitmap 表示する画像データ //! return なし void SetImage(const std::shared_ptr<wxBitmap>& bitmap); //! @brief 画像が画面に収まるように調節する void FitImageToPanel(); private: //! @brief クライアント領域の中央の座標を取得 //! @return クライアント領域のサイズの1/2 wxPoint GetClientCenter(); //! @brief ドラッグしてズーム処理の開始 //! @param pos ドラッグ開始時のマウス座標 //! @return なし void ZoomStart(const wxPoint& pos); //! @brief ドラッグしてズーム処理の終了 //! @return なし void ZoomStop(); //! @brief ドラッグ中のズーム処理 //! @param pos ドラッグ中のマウス座標 //! @return 画面更新が必要かどうか bool ZoomDuring(const wxPoint& pos); //! @brief ドラッグして移動処理の開始 //! @param pos ドラッグ開始時のマウス座標 //! @return なし void MoveStart(const wxPoint& pos); //! @brief ドラッグして移動処理の終了 //! @return なし void MoveStop(); //! @brief ドラッグして移動中の処理 //! @param pos ドラッグ中のマウス座標 //! @return 画面更新が必要かどうか bool MoveDuring(const wxPoint& pos); //! @brief ドラッグして回転処理の開始 //! @param pos ドラッグ開始時のマウス座標 //! @return なし void RotateStart(const wxPoint& pos); //! @brief ドラッグして回転処理の終了 //! @return なし void RotateStop(); //! @brief ドラッグして回転中の処理 //! @param pos ドラッグ中のマウス座標 //! @return 画面更新が必要かどうか bool RotateDuring(const wxPoint& pos); };
#include "ImagePanel.hpp" ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
void RotateAroundPoint(wxAffineMatrix2D& transform, const wxPoint2DDouble& imageCoordPos, const double rad) { // 基準点 (px, py) を原点に移動 transform.Translate(imageCoordPos.m_x, imageCoordPos.m_y); // 回転を適用 (角度はラジアン単位) transform.Rotate(rad); // 元の位置に戻す transform.Translate(-imageCoordPos.m_x, -imageCoordPos.m_y); } void TranslateInScreenSpace(wxAffineMatrix2D& transform, const wxPoint& diff) { // アフィン変換行列の現在の回転・スケーリングを取得 wxMatrix2D mat2D; wxPoint2DDouble translation; transform.Get(&mat2D, &translation); // 回転を取り除くために逆回転行列を作成 double rad = std::atan2(mat2D.m_12, mat2D.m_11); // 現在の回転角を計算(ラジアン) wxAffineMatrix2D inverseRotation; inverseRotation.Rotate(-rad); // スケーリング値を計算 double scaleX = std::sqrt(mat2D.m_11 * mat2D.m_11 + mat2D.m_21 * mat2D.m_21); double scaleY = std::sqrt(mat2D.m_12 * mat2D.m_12 + mat2D.m_22 * mat2D.m_22); // 移動量 (dx, dy) を回転の影響を受けない座標系に変換 wxPoint2DDouble adjustedMove = inverseRotation.TransformPoint(wxPoint2DDouble(diff.x, diff.y)); // 変換行列に移動を適用 transform.Translate(adjustedMove.m_x / scaleX, adjustedMove.m_y / scaleX); } wxPoint2DDouble ClientToImage(const wxPoint& clientPos, const wxAffineMatrix2D& transform) { wxAffineMatrix2D inverse = transform; inverse.Invert(); wxPoint2DDouble imagePos = inverse.TransformPoint(wxPoint2DDouble(clientPos)); return imagePos; } void Zoom(wxAffineMatrix2D& transform, double scalefactor, const wxPoint clientpos) { // クライアント座標を元画像の座標系に変換 wxPoint2DDouble imagePos = ClientToImage(clientpos, transform); // スケーリングの中心をマウス座標に固定 transform.Translate(imagePos.m_x, imagePos.m_y); transform.Scale(scalefactor, scalefactor); transform.Translate(-imagePos.m_x, -imagePos.m_y); } double CalcResizeScaleFactor(const wxSize& oldSize, const wxSize& newSize) { double diffx = newSize.GetWidth() - oldSize.GetWidth(); double diffy = newSize.GetHeight() - oldSize.GetHeight(); double scalex = newSize.GetWidth() / (double)oldSize.GetWidth(); double scaley = newSize.GetHeight() / (double)oldSize.GetHeight(); double scalefactor = 1.0; if ((int)diffy != 0) scalefactor = scaley; return scalefactor; } double CalcDragScaleFactor(const wxAffineMatrix2D& transform, const wxPoint& oldpos, const wxPoint& newpos, const int pixelratio) { // 原点からの距離を計算 double olddist = std::sqrt(oldpos.x * oldpos.x + oldpos.y * oldpos.y); double newdist = std::sqrt(newpos.x * newpos.x + newpos.y * newpos.y); double scalefactor = 1.0; if (olddist < newdist) { // 拡大 scalefactor = 1.0 + (newdist - olddist) / pixelratio; } else { // 縮小 scalefactor = 1.0 - (olddist - newdist) / pixelratio; } return scalefactor; } void ResizeKeepCenter(wxAffineMatrix2D& transform, const wxSize& oldSize, const wxSize& newSize) { wxPoint oldwndCenter = wxPoint(oldSize.GetWidth() / 2, oldSize.GetHeight() / 2); wxPoint newwndCenter = wxPoint(newSize.GetWidth() / 2, newSize.GetHeight() / 2); // リサイズ前の画面中央に表示されていた画像の座標を取得 wxPoint2DDouble oldImageCenter = ClientToImage(oldwndCenter, transform); // 画面サイズの変化に応じた新しい中央座標を取得 wxPoint2DDouble newImageCenter = ClientToImage(newwndCenter, transform); // 変位を計算し、元の画像位置を新しい中央に移動 wxPoint2DDouble shift = oldImageCenter - newImageCenter; transform.Translate(-shift.m_x, -shift.m_y); } void FitImage(wxAffineMatrix2D& transform, const wxSize& imageSize, const wxSize& panelSize) { double scaleX = static_cast<double>(panelSize.GetWidth()) / imageSize.GetWidth(); double scaleY = static_cast<double>(panelSize.GetHeight()) / imageSize.GetHeight(); double scale = std::min(scaleX, scaleY); // 中心を計算 wxPoint offset( (panelSize.GetWidth() - imageSize.GetWidth() * scale) / 2, (panelSize.GetHeight() - imageSize.GetHeight() * scale) / 2); // アフィン変換を設定 transform = wxAffineMatrix2D(); transform.Translate(offset.x, offset.y); transform.Scale(scale, scale); } double CalculateAngle(const wxPoint& a, const wxPoint& b, const wxPoint& c) { wxRealPoint ab(b.x - a.x, b.y - a.y); wxRealPoint cb(b.x - c.x, b.y - c.y); // 内積を計算 double dotProduct = ab.x * cb.x + ab.y * cb.y; // ベクトルの大きさを計算 double magnitudeAB = std::sqrt(ab.x * ab.x + ab.y * ab.y); double magnitudeCB = std::sqrt(cb.x * cb.x + cb.y * cb.y); // コサイン double cosTheta = dotProduct / (magnitudeAB * magnitudeCB); cosTheta = std::min(1.0, std::max(-1.0, cosTheta)); // 角度を計算(ラジアン) double angleRad = std::acos(cosTheta); // 外積を使って角度の符号を決定 double cross = (b.x - a.x) * (c.y - b.y) - (b.y - a.y) * (c.x - b.x); if (cross < 0.0) angleRad = -angleRad; return angleRad;// angleRad * 180.0 / M_PI; }
////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ImagePanel::ImagePanel(wxWindow* parent) : wxPanel(parent) { SetBackgroundStyle(wxBG_STYLE_PAINT); SetBackgroundColour(wxColour(0, 0, 0)); _transform = wxAffineMatrix2D(); _drag_mode = DragMode::NONE; EventBind(); } void ImagePanel::OnPaint(wxPaintEvent& event) { wxAutoBufferedPaintDC dc(this); dc.Clear(); std::shared_ptr<wxBitmap> pbitmap = _imageBitmap.lock(); if (!pbitmap) return; wxGraphicsContext* gc = wxGraphicsContext::Create(dc); if (gc) { wxGraphicsMatrix matrix = gc->CreateMatrix(_transform); gc->SetTransform(matrix); gc->DrawBitmap(*pbitmap, 0, 0, pbitmap->GetWidth(), pbitmap->GetHeight()); delete gc; } } void ImagePanel::OnEventMoveStart(wxMouseEvent& event) { MoveStart(event.GetPosition()); CaptureMouse(); } void ImagePanel::OnEventMoveStop(wxMouseEvent& event) { MoveStop(); if (HasCapture()) ReleaseMouse(); } void ImagePanel::OnEventRotateStart(wxMouseEvent& event) { RotateStart(event.GetPosition()); CaptureMouse(); } void ImagePanel::OnEventRotateStop(wxMouseEvent& event) { RotateStop(); if (HasCapture()) ReleaseMouse(); } void ImagePanel::OnEventScaleStart(wxMouseEvent& event) { ZoomStart(event.GetPosition()); CaptureMouse(); } void ImagePanel::OnEventScaleStop(wxMouseEvent& event) { ZoomStop(); if (HasCapture()) ReleaseMouse(); } void ImagePanel::OnMouseMove(wxMouseEvent& event) { if (_mov_dragging.During()) { switch (_drag_mode) { case DragMode::MOVE: if (MoveDuring(event.GetPosition())) { Refresh(); } break; case DragMode::ROTATE: if (RotateDuring(event.GetPosition())) { Refresh(); } break; case DragMode::ZOOM: if (ZoomDuring(event.GetPosition())) { Refresh(); } break; } } } void ImagePanel::OnEventScaleChange(wxMouseEvent& event) { int wheelRotation = event.GetWheelRotation(); // ホイールの回転量によって拡大・縮小率を変更 double scaleFactor = (wheelRotation > 0) ? 1.1 : 0.9; // マウスのクライアント座標を取得 wxPoint clientPos = event.GetPosition(); Zoom(_transform, scaleFactor, clientPos); Refresh(); } void ImagePanel::OnResize(wxSizeEvent& event) { if (GetClientSize().GetWidth() == 0 || GetClientSize().GetHeight() == 0) return; if (_imageBitmap.lock()) { wxSize oldSize = _winsize_back; wxSize newSize = GetClientSize(); ResizeKeepCenter(_transform, oldSize, newSize); double scalefactor = CalcResizeScaleFactor(oldSize, newSize); if ((scalefactor > 0.001) && !isnan(scalefactor) && !isinf(scalefactor)) Zoom(_transform, scalefactor, GetClientCenter()); } // 旧ウィンドウサイズを記録 _winsize_back = GetClientSize(); Refresh(); } ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// void ImagePanel::EventBind() { Bind(wxEVT_PAINT, &ImagePanel::OnPaint, this); // 移動に割り当て Bind(wxEVT_LEFT_DOWN, &ImagePanel::OnEventMoveStart, this); Bind(wxEVT_LEFT_UP, &ImagePanel::OnEventMoveStop, this); // 回転に割り当て Bind(wxEVT_RIGHT_DOWN, &ImagePanel::OnEventRotateStart, this); Bind(wxEVT_RIGHT_UP, &ImagePanel::OnEventRotateStop, this); // ホイールドラッグによる拡大・縮小 Bind(wxEVT_MIDDLE_DOWN, &ImagePanel::OnEventScaleStart, this); Bind(wxEVT_MIDDLE_UP, &ImagePanel::OnEventScaleStop, this); /* // 拡大・縮小に割り当て Bind(wxEVT_MOUSEWHEEL, &ImagePanel::OnEventScaleChange, this); */ Bind(wxEVT_MOTION, &ImagePanel::OnMouseMove, this); Bind(wxEVT_SIZE, &ImagePanel::OnResize, this); } void ImagePanel::SetImage(const std::shared_ptr<wxBitmap>& bitmap) { _imageBitmap = bitmap; Refresh(); } void ImagePanel::FitImageToPanel() { std::shared_ptr<wxBitmap> pbitmap = _imageBitmap.lock(); if (!pbitmap) return; // スケール計算 wxSize panelSize = GetClientSize(); wxSize imageSize = pbitmap->GetSize(); FitImage(_transform, imageSize, panelSize); _winsize_back = GetClientSize(); Refresh(); } wxPoint ImagePanel::GetClientCenter() { wxSize panelSize = GetClientSize(); return wxPoint(panelSize.GetWidth() / 2, panelSize.GetHeight() / 2); } void ImagePanel::ZoomStart(const wxPoint& pos) { _drag_mode = DragMode::ZOOM; _transform_back = _transform; _mov_dragging.Start(pos); } void ImagePanel::ZoomStop() { _mov_dragging.Stop(); } bool ImagePanel::ZoomDuring(const wxPoint& pos) { _mov_dragging.Update(pos); wxPoint oldpos = _mov_dragging.GetFrom(); wxPoint newpos = _mov_dragging.GetTo(); wxPoint diff = _mov_dragging.Difference(); if (diff.x == 0 && diff.y == 0) { return false; } wxPoint winoldpos = ClientToScreen(oldpos); wxPoint winnewpos = ClientToScreen(newpos); double scalefactor = CalcDragScaleFactor(_transform, winoldpos, winnewpos, GetClientSize().GetWidth()); if (!isnan(scalefactor) && !isinf(scalefactor)) { _transform = _transform_back; Zoom(_transform, scalefactor, oldpos); } else { return false; } return true; } void ImagePanel::MoveStart(const wxPoint& pos) { _drag_mode = DragMode::MOVE; _transform_back = _transform; _mov_dragging.Start(pos); } void ImagePanel::MoveStop() { _mov_dragging.Stop(); } bool ImagePanel::MoveDuring(const wxPoint& pos) { _mov_dragging.Update(pos); wxPoint diff = _mov_dragging.Difference(); if (diff.x == 0 && diff.y == 0) return false; _transform = _transform_back; // 移動量を画面座標系で適用 TranslateInScreenSpace(_transform, diff); return true; } void ImagePanel::RotateStart(const wxPoint& pos) { _drag_mode = DragMode::ROTATE; _transform_back = _transform; // クライアント領域の中心座標を取得 wxSize panelSize = GetClientSize(); _mov_dragging = MouseDrag(GetClientCenter()); _mov_dragging.Start(pos); } void ImagePanel::RotateStop() { _mov_dragging.Stop(); } bool ImagePanel::RotateDuring(const wxPoint& pos) { _mov_dragging.Update(pos); double rad = _mov_dragging.Angle(); if (isnan(rad) || isinf(rad)) return false; _transform = _transform_back; auto imgpos = ClientToImage(GetClientCenter(), _transform); RotateAroundPoint(_transform, imgpos, rad); return true; }
// https://docs.wxwidgets.org/3.0/overview_helloworld.html // プリプロセッサに以下二つを追加 // __WXMSW__ // WXUSINGDLL // サブシステムをWindowsに設定(WinMainで呼び出すので) // Windows (/SUBSYSTEM:WINDOWS) #ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// #include <string> #include "ImagePanel.hpp" // ウィンドウ作成 class MyFrame : public wxFrame { ImagePanel* _imagePanel;// 画像表示パネル std::shared_ptr<wxBitmap> _bitmap; public: void PostCreate() { // wxImage::AddHandler(new wxPNGHandler); _bitmap = std::make_shared<wxBitmap>(); wxImage image; if (image.LoadFile(R"(C:\temp\test.png)", wxBITMAP_TYPE_PNG)) {// 画像読み込み *_bitmap = wxBitmap(image); } this->Layout(); // レイアウトの更新 _imagePanel->SetImage(_bitmap);// パネルに画像を指定 _imagePanel->FitImageToPanel(); } MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) { // CallAfter : 現在処理中のイベントが終わったらPostCreateを実行 // コンストラクタはウィンドウ生成イベント扱い CallAfter(&MyFrame::PostCreate); // ImagePanel作成 _imagePanel = new ImagePanel(this); // 画面いっぱいに貼り付ける wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(_imagePanel, 1, wxEXPAND); this->SetSizer(sizer); } private: }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // wxWidgetsのアプリケーション作成 class MyApp : public wxApp { public: virtual bool OnInit() { MyFrame* frame = new MyFrame("Hello World", wxPoint(50, 50), wxSize(450, 340)); frame->Show(true); return true; } }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // WinMainをマクロで定義 wxIMPLEMENT_APP(MyApp);
libzipでファイル一覧を取得すると、元のディレクトリ構造が取得できるが、ツリーになっていない。これをツリーに変換する。
#include <iostream> #include <vector> #include <string> #include <sstream> #include <filesystem> // SetConsoleOutputCP のために必要 #include <windows.h> // libzip #include <zip.h> #pragma comment(lib,"zip.lib")
enum class FileType { Ignore, Directory, File };
struct DirTree { std::string name; FileType type; std::vector<DirTree> children; DirTree(const std::string& name, const FileType type) : name(name), type(type){} };
// 既に登録済みかどうかをチェックし、既にあるノードならイテレータを返す std::vector<DirTree>::iterator getNodeIfAlreadyRegistered(DirTree& root, const std::string& name) { return std::find_if( root.children.begin(), root.children.end(), [&](const DirTree& child) { return child.name == name; } ); }
void AddPath(DirTree& root, const std::string& path) { namespace fs = std::filesystem; std::istringstream ss(path); std::string segment; DirTree* current = &root; // 次の'/'までのパスを取得 while (std::getline(ss, segment, '/')) { // 過去にツリーに登録されたノードかどうかをチェック auto currentnode = getNodeIfAlreadyRegistered(*current, segment); // すでに登録済みなら次回以降のノードはそのノードより下に追加 if (currentnode != current->children.end()) { current = &(*currentnode); } else { // 最後の要素以外はディレクトリであることは自明なので、ディレクトリとして登録 current->children.push_back({ segment, FileType::Directory }); current = ¤t->children.back(); } } // 最後の要素はファイルの可能性があるので、pathに'/'があるかどうかで判別 bool isDir = path.back() == '/'; current->type = isDir ? FileType::Directory : FileType::File; }
// ファイルリストをツリー構造に変換 DirTree BuildDirTree(const std::vector<std::string>& files) { DirTree root( "root", FileType::Ignore); for (const auto& file : files) { AddPath(root, file); } return root; }
// ファイルツリーを表示 void printTree(const DirTree& tree, int depth = 0) { auto to_string = [](const FileType type) { switch (type) { case FileType::Ignore: return "*"; case FileType::Directory: return "[DIR]"; case FileType::File: return "[FILE]"; } return "Unknown"; }; std::string indent(depth * 2, ' '); std::cout << indent << to_string(tree.type) << tree.name << "\n"; for (const auto& child : tree.children) { printTree(child, depth + 1); } }
std::vector<std::string> FilesInZip(const char* pathname) { std::vector<std::string> files; // エラーコードを格納する変数 int error = 0; // zipファイルを読み取り専用でオープン zip_t* zipArchive = zip_open(pathname, ZIP_RDONLY, &error); if (zipArchive == nullptr) { return std::vector<std::string>(); } // zipファイル内のエントリ数を取得 zip_int64_t numEntries = zip_get_num_entries(zipArchive, 0); // 各エントリの名前を取得して表示 for (zip_uint64_t i = 0; i < static_cast<zip_uint64_t>(numEntries); ++i) { const char* entryName = zip_get_name(zipArchive, i, 0); files.emplace_back(entryName); // zipファイル内のファイル名を格納 } // zipファイルをクローズ zip_close(zipArchive); return files; }
int main() { std::vector<std::string> files = FilesInZip((const char*)u8R"(C:\test\ConsoleApplication31.zip)"); // windowsのコンソールでUTF-8を表示するために必要 SetConsoleOutputCP(CP_UTF8); // ファイルのリストをツリー構造に変換 DirTree root = BuildDirTree(files); printTree(root); for(auto& f : files) { std::cout << f << std::endl; } }
まずfilesをそのまま表示すると以下のようになる。
これをツリーにして、printTreeで出力すると以下になる。
*root [FILE]ConsoleApplication31.sln [DIR].vs [DIR]ConsoleApplication31 [DIR]copilot-chat [DIR]ff91ad84 [DIR]sessions [DIR]FileContentIndex [FILE]b5c23407-6b8a-446b-82cf-97b7abb15edd.vsidx [DIR]v17 [FILE].suo [FILE]Browse.VC.db [FILE]DocumentLayout.json