ren-ui-python 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,764 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // ABI v5. Only opaque application pointers and integer object IDs cross the ABI.
3
+ #include <Alert.h>
4
+ #include <Application.h>
5
+ #include <Button.h>
6
+ #include <Bitmap.h>
7
+ #include <Entry.h>
8
+ #include <File.h>
9
+ #include <FilePanel.h>
10
+ #include <IconUtils.h>
11
+ #include <Looper.h>
12
+ #include <TranslationUtils.h>
13
+ #include <CheckBox.h>
14
+ #include <Clipboard.h>
15
+ #include <FindDirectory.h>
16
+ #include <GroupLayout.h>
17
+ #include <GroupView.h>
18
+ #include <Message.h>
19
+ #include <Messenger.h>
20
+ #include <Path.h>
21
+ #include <Roster.h>
22
+ #include <ScrollView.h>
23
+ #include <Slider.h>
24
+ #include <StringView.h>
25
+ #include <TextControl.h>
26
+ #include <TextView.h>
27
+ #include <Window.h>
28
+
29
+ #include <algorithm>
30
+ #include <cmath>
31
+ #include <vector>
32
+ #include <atomic>
33
+ #include <cstdint>
34
+ #include <cstring>
35
+ #include <map>
36
+ #include <memory>
37
+ #include <mutex>
38
+ #include <stdexcept>
39
+ #include <string>
40
+
41
+ namespace {
42
+ thread_local std::string error;
43
+ thread_local std::string result;
44
+ using Callback = void (*)(int64_t, int64_t, const char*, const char*, int);
45
+ constexpr uint32 kControl = 'renC';
46
+ constexpr uint32 kCloseAccepted = 'renQ';
47
+
48
+ void check(status_t status, const char* operation)
49
+ {
50
+ if (status < B_OK)
51
+ throw std::runtime_error(std::string(operation) + ": " + strerror(status));
52
+ }
53
+
54
+ // Decode before replacing a live image, so errors keep the previous bitmap.
55
+ std::unique_ptr<BBitmap> loadBitmap(const char* path, int vectorSize)
56
+ {
57
+ BFile file(path, B_READ_ONLY);
58
+ check(file.InitCheck(), "Open image");
59
+ char magic[4];
60
+ bool vector = file.Read(magic, sizeof(magic)) == 4 && !memcmp(magic, "ncif", 4);
61
+ std::unique_ptr<BBitmap> bitmap;
62
+ if (vector) {
63
+ off_t size;
64
+ check(file.GetSize(&size), "Read HVIF size");
65
+ if (size < 7 || size > 64 * 1024)
66
+ throw std::runtime_error("HVIF must be between 7 bytes and 64 KiB");
67
+ std::vector<uint8> data(static_cast<size_t>(size));
68
+ if (file.ReadAt(0, data.data(), data.size()) != size)
69
+ throw std::runtime_error("Could not read HVIF");
70
+ bitmap.reset(new BBitmap(BRect(0, 0, vectorSize - 1, vectorSize - 1), B_RGBA32));
71
+ check(bitmap->InitCheck(), "HVIF bitmap");
72
+ check(BIconUtils::GetVectorIcon(data.data(), data.size(), bitmap.get()), "Decode HVIF");
73
+ } else {
74
+ bitmap.reset(BTranslationUtils::GetBitmapFile(path));
75
+ if (!bitmap) throw std::runtime_error(std::string("Cannot decode image: ") + path);
76
+ check(bitmap->InitCheck(), "Image bitmap");
77
+ }
78
+ if (bitmap->Bounds().Width() >= 16384 || bitmap->Bounds().Height() >= 16384
79
+ || bitmap->BitsLength() > 64 * 1024 * 1024)
80
+ throw std::runtime_error("Decoded image exceeds 16384 pixels or 64 MiB");
81
+ return bitmap;
82
+ }
83
+
84
+ BRect imageRect(BRect source, BRect target, const std::string& fit)
85
+ {
86
+ if (fit == "stretch") return target;
87
+ float sx = (target.Width() + 1) / (source.Width() + 1);
88
+ float sy = (target.Height() + 1) / (source.Height() + 1);
89
+ float scale = fit == "cover" ? std::max(sx, sy) : std::min(sx, sy);
90
+ float width = (source.Width() + 1) * scale;
91
+ float height = (source.Height() + 1) * scale;
92
+ float x = target.left + (target.Width() + 1 - width) / 2;
93
+ float y = target.top + (target.Height() + 1 - height) / 2;
94
+ return BRect(x, y, x + width - 1, y + height - 1);
95
+ }
96
+
97
+ class ImageView : public BView {
98
+ std::unique_ptr<BBitmap> bitmap;
99
+ std::string fit;
100
+ int vectorSize;
101
+ public:
102
+ ImageView(const char* name, const char* path, float width, float height, const char* mode)
103
+ : BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), fit(mode),
104
+ vectorSize(0)
105
+ {
106
+ if (width <= 0 || height <= 0 || width > 4096 || height > 4096
107
+ || !std::isfinite(width) || !std::isfinite(height)
108
+ || (fit != "contain" && fit != "cover" && fit != "stretch"))
109
+ throw std::runtime_error("Invalid image viewport");
110
+ vectorSize = static_cast<int>(std::ceil(std::max(width, height)));
111
+ bitmap = loadBitmap(path, vectorSize);
112
+ // Haiku's BSize uses coordinate distances (pixel count minus one).
113
+ BSize size(width - 1, height - 1);
114
+ SetExplicitMinSize(size);
115
+ SetExplicitPreferredSize(size);
116
+ SetExplicitMaxSize(size);
117
+ SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
118
+ }
119
+
120
+ void Replace(const char* path)
121
+ {
122
+ auto next = loadBitmap(path, vectorSize);
123
+ bitmap.swap(next);
124
+ Invalidate();
125
+ }
126
+
127
+ void Draw(BRect) override
128
+ {
129
+ SetDrawingMode(B_OP_COPY);
130
+ SetHighColor(ViewColor());
131
+ FillRect(Bounds());
132
+ SetDrawingMode(B_OP_ALPHA);
133
+ SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
134
+ DrawBitmap(bitmap.get(), bitmap->Bounds(), imageRect(bitmap->Bounds(), Bounds(), fit),
135
+ B_FILTER_BITMAP_BILINEAR);
136
+ }
137
+ };
138
+
139
+ std::unique_ptr<BBitmap> buttonBitmap(const char* path, int size)
140
+ {
141
+ auto source = loadBitmap(path, size);
142
+ auto scaled = std::make_unique<BBitmap>(BRect(0, 0, size - 1, size - 1),
143
+ B_BITMAP_ACCEPTS_VIEWS, B_RGBA32);
144
+ check(scaled->InitCheck(), "Icon bitmap");
145
+ // Start transparent so non-square icons retain their aspect ratio.
146
+ memset(scaled->Bits(), 0, scaled->BitsLength());
147
+ auto painter = std::make_unique<BView>(scaled->Bounds(), "icon", B_FOLLOW_NONE, B_WILL_DRAW);
148
+ if (!scaled->Lock()) throw std::runtime_error("Cannot lock icon bitmap");
149
+ scaled->AddChild(painter.get());
150
+ painter->SetDrawingMode(B_OP_ALPHA);
151
+ painter->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE);
152
+ painter->DrawBitmap(source.get(), source->Bounds(),
153
+ imageRect(source->Bounds(), scaled->Bounds(), "contain"), B_FILTER_BITMAP_BILINEAR);
154
+ painter->Sync();
155
+ scaled->RemoveChild(painter.get());
156
+ scaled->Unlock();
157
+ return scaled;
158
+ }
159
+
160
+ struct Context {
161
+ BApplication* app = nullptr;
162
+ BMessenger messenger;
163
+ Callback callback = nullptr;
164
+ std::mutex mutex;
165
+ std::map<int64_t, BMessenger> windows;
166
+ std::atomic<int> live{0};
167
+ std::atomic<bool> destroying{false};
168
+ int64_t nextWindow = 1;
169
+ };
170
+
171
+ class Application : public BApplication {
172
+ Context* context;
173
+ public:
174
+ Application(const char* signature, Context* c)
175
+ : BApplication(signature), context(c) {}
176
+
177
+ bool QuitRequested() override
178
+ {
179
+ if (context->live == 0 || context->destroying)
180
+ return true;
181
+ // BApplication's default stops at the first asynchronous window veto.
182
+ // Ask Python to request every window, then exit after the last closes.
183
+ context->callback(0, 0, "quit_requested", "", 0);
184
+ return false;
185
+ }
186
+ };
187
+
188
+ class TextArea : public BTextView {
189
+ Context* context;
190
+ int64_t windowID;
191
+ int64_t viewID;
192
+ bool silent = false;
193
+
194
+ void Changed()
195
+ {
196
+ // Only snapshot into Python's queue; never execute user code here.
197
+ if (!silent && BView::Window())
198
+ context->callback(windowID, viewID, "change", Text(), 0);
199
+ }
200
+
201
+ protected:
202
+ void InsertText(const char* text, int32 length, int32 offset,
203
+ const text_run_array* runs) override
204
+ {
205
+ BTextView::InsertText(text, length, offset, runs);
206
+ Changed();
207
+ }
208
+
209
+ void DeleteText(int32 from, int32 to) override
210
+ {
211
+ BTextView::DeleteText(from, to);
212
+ Changed();
213
+ }
214
+
215
+ public:
216
+ TextArea(const char* name, Context* c, int64_t wid, int64_t id, bool wrap)
217
+ : BTextView(name, B_WILL_DRAW | B_PULSE_NEEDED | B_NAVIGABLE),
218
+ context(c), windowID(wid), viewID(id)
219
+ {
220
+ SetWordWrap(wrap);
221
+ SetDoesUndo(true);
222
+ SetMaxBytes(1024 * 1024);
223
+ SetInsets(8, 8, 8, 8);
224
+ SetExplicitMinSize(BSize(240, 180));
225
+ SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
226
+ }
227
+
228
+ void ReplaceText(const char* text)
229
+ {
230
+ if (strlen(text) > static_cast<size_t>(MaxBytes()))
231
+ throw std::runtime_error("TextArea supports at most 1 MiB of UTF-8 text");
232
+ silent = true;
233
+ try {
234
+ SetText(text);
235
+ } catch (...) {
236
+ silent = false;
237
+ throw;
238
+ }
239
+ silent = false;
240
+ Changed(); // Exactly one snapshot for a programmatic replacement.
241
+ }
242
+ };
243
+
244
+ class Window : public BWindow {
245
+ public:
246
+ Context* context;
247
+ int64_t id;
248
+ float initialWidth;
249
+ float initialHeight;
250
+ int64_t nextView = 1;
251
+ bool closePending = false;
252
+ std::map<int64_t, BView*> views;
253
+
254
+ Window(Context* c, int64_t n, const char* title, float width, float height, bool resize)
255
+ : BWindow(BRect(80, 80, 80 + width, 80 + height), title, B_TITLED_WINDOW,
256
+ B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS
257
+ | (resize ? 0 : B_NOT_RESIZABLE | B_NOT_ZOOMABLE)), context(c), id(n),
258
+ initialWidth(width), initialHeight(height)
259
+ {
260
+ SetLayout(new BGroupLayout(B_VERTICAL, 0));
261
+ ++context->live;
262
+ }
263
+
264
+ ~Window() override
265
+ {
266
+ context->callback(id, 0, "closed", "", 0);
267
+ if (--context->live == 0 && !context->destroying)
268
+ context->messenger.SendMessage(B_QUIT_REQUESTED);
269
+ }
270
+
271
+ bool QuitRequested() override
272
+ {
273
+ // Decide on Python's dispatcher without holding this looper's lock.
274
+ // In particular, a synchronous BAlert::Go() must not run here.
275
+ if (!closePending) {
276
+ closePending = true;
277
+ context->callback(id, 0, "close_requested", "", 0);
278
+ }
279
+ return false;
280
+ }
281
+
282
+ void MessageReceived(BMessage* message) override
283
+ {
284
+ if (message->what == kCloseAccepted) {
285
+ Quit(); // Already approved; do not ask QuitRequested a second time.
286
+ return;
287
+ }
288
+ if (message->what != kControl) {
289
+ BWindow::MessageReceived(message);
290
+ return;
291
+ }
292
+ int64 viewID;
293
+ if (message->FindInt64("ren:id", &viewID) != B_OK)
294
+ return;
295
+ auto found = views.find(viewID);
296
+ if (found == views.end())
297
+ return;
298
+ BControl* control = dynamic_cast<BControl*>(found->second);
299
+ if (!control)
300
+ return;
301
+ BTextControl* text = dynamic_cast<BTextControl*>(control);
302
+ context->callback(id, viewID, dynamic_cast<BButton*>(control) ? "click" : "change",
303
+ text ? text->Text() : (control->Label() ? control->Label() : ""), control->Value());
304
+ }
305
+ };
306
+
307
+ // Lock through BMessenger, never dereference a raw pointer to a closed window.
308
+ class WindowLock {
309
+ public:
310
+ Window* window = nullptr;
311
+ WindowLock(Context* context, int64_t id)
312
+ {
313
+ BMessenger messenger;
314
+ {
315
+ std::lock_guard<std::mutex> lock(context->mutex);
316
+ auto found = context->windows.find(id);
317
+ if (found == context->windows.end())
318
+ throw std::runtime_error("Unknown window");
319
+ messenger = found->second;
320
+ }
321
+ check(messenger.LockTargetWithTimeout(5000000), "Window has closed or is busy");
322
+ BLooper* looper = nullptr;
323
+ messenger.Target(&looper);
324
+ window = dynamic_cast<Window*>(looper);
325
+ if (!window) {
326
+ if (looper) looper->Unlock();
327
+ throw std::runtime_error("Invalid window target");
328
+ }
329
+ }
330
+ ~WindowLock() { if (window) window->Unlock(); }
331
+ BView* view(int64_t id)
332
+ {
333
+ auto found = window->views.find(id);
334
+ if (found == window->views.end())
335
+ throw std::runtime_error("Unknown widget");
336
+ return found->second;
337
+ }
338
+ BControl* control(int64_t id)
339
+ {
340
+ auto control = dynamic_cast<BControl*>(view(id));
341
+ if (!control) throw std::runtime_error("Widget is not a control");
342
+ return control;
343
+ }
344
+ };
345
+
346
+ // Receives a file panel's reply on its own looper while the caller waits.
347
+ class PanelReply : public BLooper {
348
+ public:
349
+ std::string path;
350
+ sem_id done;
351
+
352
+ PanelReply() : BLooper("ren_file_panel"), done(create_sem(0, "ren_file_panel")) {}
353
+ ~PanelReply() override { delete_sem(done); }
354
+
355
+ void MessageReceived(BMessage* message) override
356
+ {
357
+ entry_ref ref;
358
+ const char* name;
359
+ switch (message->what) {
360
+ case B_REFS_RECEIVED:
361
+ if (message->FindRef("refs", &ref) == B_OK)
362
+ path = BPath(&ref).Path();
363
+ break;
364
+ case B_SAVE_REQUESTED:
365
+ if (message->FindRef("directory", &ref) == B_OK
366
+ && message->FindString("name", &name) == B_OK) {
367
+ BPath target(&ref);
368
+ if (target.Append(name) == B_OK) path = target.Path();
369
+ }
370
+ break;
371
+ case B_CANCEL:
372
+ // Sent whenever the panel hides, also after a selection.
373
+ release_sem(done);
374
+ break;
375
+ default:
376
+ BLooper::MessageReceived(message);
377
+ }
378
+ }
379
+ };
380
+
381
+ BMessage* controlMessage(int64_t id)
382
+ {
383
+ auto message = new BMessage(kControl);
384
+ message->AddInt64("ren:id", id);
385
+ return message;
386
+ }
387
+
388
+ #define BEGIN_API error.clear(); try {
389
+ #define END_API(failure) } catch (const std::exception& e) { error = e.what(); } \
390
+ catch (...) { error = "Unknown native exception"; } return failure;
391
+ } // namespace
392
+
393
+ extern "C" {
394
+ int ren_abi() { return 5; }
395
+ const char* ren_error() { return error.c_str(); }
396
+
397
+ void* ren_app_create(const char* signature, Callback callback)
398
+ {
399
+ BEGIN_API
400
+ if (be_app) throw std::runtime_error("A BApplication already exists");
401
+ auto context = std::make_unique<Context>();
402
+ auto app = std::make_unique<Application>(signature, context.get());
403
+ check(app->InitCheck(), "BApplication");
404
+ context->app = app.release();
405
+ context->messenger = BMessenger(context->app);
406
+ context->callback = callback;
407
+ return context.release();
408
+ END_API(nullptr)
409
+ }
410
+
411
+ int ren_app_run(Context* context)
412
+ {
413
+ BEGIN_API
414
+ check(context->app->Run(), "BApplication::Run");
415
+ return 0;
416
+ END_API(-1)
417
+ }
418
+
419
+ int ren_app_quit(Context* context)
420
+ {
421
+ BEGIN_API
422
+ check(context->messenger.SendMessage(B_QUIT_REQUESTED), "Application quit");
423
+ return 0;
424
+ END_API(-1)
425
+ }
426
+
427
+ int ren_app_destroy(Context* context)
428
+ {
429
+ BEGIN_API
430
+ context->destroying = true;
431
+ for (auto& entry : context->windows) {
432
+ if (entry.second.LockTarget()) {
433
+ BLooper* looper = nullptr;
434
+ entry.second.Target(&looper);
435
+ if (looper) looper->Quit(); // Deletes and unlocks the looper.
436
+ }
437
+ }
438
+ delete context->app;
439
+ delete context;
440
+ return 0;
441
+ END_API(-1)
442
+ }
443
+
444
+ int64_t ren_window_create(Context* context, const char* title, float width, float height, int resize)
445
+ {
446
+ BEGIN_API
447
+ std::lock_guard<std::mutex> lock(context->mutex);
448
+ int64_t id = context->nextWindow++;
449
+ auto window = new Window(context, id, title, width, height, resize);
450
+ context->windows.emplace(id, BMessenger(window));
451
+ // BLooper::Run starts the looper and releases its initial constructor lock.
452
+ // Starting hidden makes subsequent Show calls have ordinary locking rules.
453
+ thread_id thread = window->Run();
454
+ if (thread < B_OK) {
455
+ window->Quit();
456
+ check(thread, "BWindow::Run");
457
+ }
458
+ return id;
459
+ END_API(0)
460
+ }
461
+
462
+ int ren_window_show(Context* context, int64_t id)
463
+ {
464
+ BEGIN_API
465
+ WindowLock lock(context, id);
466
+ if (lock.window->IsHidden()) {
467
+ // Auto size limits change while the Python tree is mounted one view
468
+ // at a time. Restore the requested size after the tree is complete.
469
+ lock.window->ResizeTo(lock.window->initialWidth, lock.window->initialHeight);
470
+ lock.window->CenterOnScreen();
471
+ lock.window->Show();
472
+ }
473
+ lock.window->Activate();
474
+ return 0;
475
+ END_API(-1)
476
+ }
477
+
478
+ int ren_window_close(Context* context, int64_t id)
479
+ {
480
+ BEGIN_API
481
+ WindowLock lock(context, id);
482
+ check(lock.window->PostMessage(B_QUIT_REQUESTED), "Window close");
483
+ return 0;
484
+ END_API(-1)
485
+ }
486
+
487
+ int ren_window_close_reply(Context* context, int64_t id, int allow)
488
+ {
489
+ BEGIN_API
490
+ WindowLock lock(context, id);
491
+ if (lock.window->closePending) {
492
+ if (allow)
493
+ check(lock.window->PostMessage(kCloseAccepted), "Approve window close");
494
+ else
495
+ lock.window->closePending = false;
496
+ }
497
+ return 0;
498
+ END_API(-1)
499
+ }
500
+
501
+ int ren_window_title(Context* context, int64_t id, const char* title)
502
+ {
503
+ BEGIN_API
504
+ WindowLock lock(context, id);
505
+ lock.window->SetTitle(title);
506
+ return 0;
507
+ END_API(-1)
508
+ }
509
+
510
+ int64_t ren_view_create(Context* context, int64_t wid, int64_t parent,
511
+ const char* kind, const char* text, const char* label, float a, float b)
512
+ {
513
+ BEGIN_API
514
+ WindowLock lock(context, wid);
515
+ BGroupLayout* layout = parent == 0
516
+ ? dynamic_cast<BGroupLayout*>(lock.window->GetLayout())
517
+ : dynamic_cast<BGroupLayout*>(lock.view(parent)->GetLayout());
518
+ if (!layout) throw std::runtime_error("Parent is not a group layout");
519
+ int64_t id = lock.window->nextView++;
520
+ std::string name = "ren_" + std::to_string(id);
521
+ std::unique_ptr<BView> view;
522
+ BView* exposed = nullptr;
523
+ if (!strcmp(kind, "column") || !strcmp(kind, "row")) {
524
+ auto group = new BGroupView(name.c_str(), !strcmp(kind, "row") ? B_HORIZONTAL : B_VERTICAL, a);
525
+ group->GroupLayout()->SetInsets(b, b, b, b);
526
+ // Rows of fixed-size controls must not cap the containing window at
527
+ // their preferred size. Honor Window's requested size and resizing.
528
+ group->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
529
+ !strcmp(kind, "row") ? B_SIZE_UNSET : B_SIZE_UNLIMITED));
530
+ view.reset(group);
531
+ } else if (!strcmp(kind, "image")) {
532
+ view.reset(new ImageView(name.c_str(), text, a, b, label));
533
+ } else if (!strcmp(kind, "label")) {
534
+ view.reset(new BStringView(name.c_str(), text));
535
+ } else if (!strcmp(kind, "button")) {
536
+ view.reset(new BButton(name.c_str(), text, controlMessage(id)));
537
+ } else if (!strcmp(kind, "checkbox")) {
538
+ view.reset(new BCheckBox(name.c_str(), text, controlMessage(id)));
539
+ } else if (!strcmp(kind, "text")) {
540
+ auto input = new BTextControl(name.c_str(), label, text, controlMessage(id));
541
+ input->SetModificationMessage(controlMessage(id));
542
+ view.reset(input);
543
+ } else if (!strcmp(kind, "textarea")) {
544
+ auto input = std::make_unique<TextArea>(name.c_str(), context, wid, id, a != 0);
545
+ input->ReplaceText(text);
546
+ exposed = input.get();
547
+ // The scroll view owns its target, but public property access uses the
548
+ // inner text view. Both are destroyed with the containing window.
549
+ view.reset(new BScrollView((name + "_scroll").c_str(), input.get(),
550
+ uint32(0), a == 0, true, B_FANCY_BORDER));
551
+ input.release();
552
+ } else if (!strcmp(kind, "slider")) {
553
+ auto slider = new BSlider(name.c_str(), text, controlMessage(id), (int32)a, (int32)b, B_HORIZONTAL);
554
+ slider->SetModificationMessage(controlMessage(id));
555
+ view.reset(slider);
556
+ } else {
557
+ throw std::runtime_error("Unsupported widget type");
558
+ }
559
+ BView* raw = view.get();
560
+ if (!layout->AddView(raw)) throw std::runtime_error("Could not attach widget");
561
+ view.release(); // The window hierarchy now owns the view.
562
+ lock.window->views.emplace(id, exposed ? exposed : raw);
563
+ if (auto control = dynamic_cast<BControl*>(raw))
564
+ check(control->SetTarget(lock.window), "Control target");
565
+ return id;
566
+ END_API(0)
567
+ }
568
+
569
+ int ren_image_source(Context* context, int64_t wid, int64_t id, const char* path)
570
+ {
571
+ BEGIN_API
572
+ WindowLock lock(context, wid);
573
+ auto image = dynamic_cast<ImageView*>(lock.view(id));
574
+ if (!image) throw std::runtime_error("Widget is not an image");
575
+ image->Replace(path);
576
+ return 0;
577
+ END_API(-1)
578
+ }
579
+
580
+ int ren_button_icon(Context* context, int64_t wid, int64_t id, const char* path, int size)
581
+ {
582
+ BEGIN_API
583
+ if (size < 1 || size > 256) throw std::runtime_error("Invalid button icon size");
584
+ WindowLock lock(context, wid);
585
+ auto button = dynamic_cast<BButton*>(lock.view(id));
586
+ if (!button) throw std::runtime_error("Widget is not a button");
587
+ auto bitmap = path[0] ? buttonBitmap(path, size) : std::unique_ptr<BBitmap>();
588
+ // BButton copies the bitmap and supplies its native disabled/pressed states.
589
+ check(button->SetIcon(bitmap.get()), "Set button icon");
590
+ return 0;
591
+ END_API(-1)
592
+ }
593
+
594
+ int ren_view_set_text(Context* context, int64_t wid, int64_t id, const char* text)
595
+ {
596
+ BEGIN_API
597
+ WindowLock lock(context, wid);
598
+ BView* view = lock.view(id);
599
+ if (auto area = dynamic_cast<TextArea*>(view)) area->ReplaceText(text);
600
+ else if (auto input = dynamic_cast<BTextControl*>(view)) input->SetText(text);
601
+ else if (auto label = dynamic_cast<BStringView*>(view)) label->SetText(text);
602
+ else if (auto control = dynamic_cast<BControl*>(view)) control->SetLabel(text);
603
+ else throw std::runtime_error("Widget has no text");
604
+ return 0;
605
+ END_API(-1)
606
+ }
607
+
608
+ const char* ren_view_text(Context* context, int64_t wid, int64_t id)
609
+ {
610
+ BEGIN_API
611
+ WindowLock lock(context, wid);
612
+ BView* view = lock.view(id);
613
+ if (auto area = dynamic_cast<TextArea*>(view)) result = area->Text();
614
+ else if (auto input = dynamic_cast<BTextControl*>(view)) result = input->Text();
615
+ else if (auto label = dynamic_cast<BStringView*>(view)) result = label->Text();
616
+ else if (auto control = dynamic_cast<BControl*>(view)) result = control->Label() ? control->Label() : "";
617
+ else throw std::runtime_error("Widget has no text");
618
+ return result.c_str();
619
+ END_API(nullptr)
620
+ }
621
+
622
+ int ren_view_set_value(Context* context, int64_t wid, int64_t id, int value)
623
+ {
624
+ BEGIN_API
625
+ WindowLock lock(context, wid);
626
+ lock.control(id)->SetValue(value);
627
+ return 0;
628
+ END_API(-1)
629
+ }
630
+
631
+ int ren_view_value(Context* context, int64_t wid, int64_t id, int* value)
632
+ {
633
+ BEGIN_API
634
+ WindowLock lock(context, wid);
635
+ *value = lock.control(id)->Value();
636
+ return 0;
637
+ END_API(-1)
638
+ }
639
+
640
+ int ren_view_enabled(Context* context, int64_t wid, int64_t id, int enabled)
641
+ {
642
+ BEGIN_API
643
+ WindowLock lock(context, wid);
644
+ if (auto area = dynamic_cast<TextArea*>(lock.view(id))) area->MakeEditable(enabled != 0);
645
+ else lock.control(id)->SetEnabled(enabled != 0);
646
+ return 0;
647
+ END_API(-1)
648
+ }
649
+
650
+ int ren_view_invoke(Context* context, int64_t wid, int64_t id)
651
+ {
652
+ BEGIN_API
653
+ WindowLock lock(context, wid);
654
+ auto control = lock.control(id);
655
+ if (control->IsEnabled()) check(control->Invoke(), "Control invocation");
656
+ return 0;
657
+ END_API(-1)
658
+ }
659
+
660
+ int ren_alert(const char* title, const char* text)
661
+ {
662
+ BEGIN_API
663
+ (new BAlert(title, text, "OK"))->Go();
664
+ return 0;
665
+ END_API(-1)
666
+ }
667
+
668
+ int ren_confirm(const char* title, const char* text, const char* accept, const char* cancel)
669
+ {
670
+ BEGIN_API
671
+ auto dialog = new BAlert(title, text, cancel, accept);
672
+ dialog->SetShortcut(0, B_ESCAPE);
673
+ return dialog->Go() == 1 ? 1 : 0;
674
+ END_API(-1)
675
+ }
676
+
677
+ const char* ren_file_panel(int save, const char* title, const char* directory, const char* name)
678
+ {
679
+ BEGIN_API
680
+ result.clear();
681
+ entry_ref start;
682
+ if (directory[0]) check(get_ref_for_path(directory, &start), "File panel directory");
683
+ auto reply = new PanelReply();
684
+ if (reply->done < B_OK || reply->Run() < B_OK) {
685
+ reply->Quit(); // Still holds its constructor lock.
686
+ throw std::runtime_error("Cannot start file panel");
687
+ }
688
+ {
689
+ BMessenger target(reply);
690
+ BFilePanel panel(save ? B_SAVE_PANEL : B_OPEN_PANEL, &target, directory[0] ? &start : nullptr,
691
+ B_FILE_NODE, false, nullptr, nullptr, true, true);
692
+ if (save && name[0]) panel.SetSaveText(name);
693
+ if (title[0] && panel.Window()->Lock()) {
694
+ panel.Window()->SetTitle(title);
695
+ panel.Window()->Unlock();
696
+ }
697
+ panel.Show();
698
+ // The panel is modal; wait here without holding any looper lock.
699
+ while (acquire_sem(reply->done) == B_INTERRUPTED) {}
700
+ }
701
+ if (reply->Lock()) {
702
+ result = reply->path;
703
+ reply->Quit();
704
+ }
705
+ return result.c_str();
706
+ END_API(nullptr)
707
+ }
708
+
709
+ int ren_clipboard_set(const char* text)
710
+ {
711
+ BEGIN_API
712
+ if (!be_clipboard->Lock()) throw std::runtime_error("Clipboard is busy");
713
+ status_t status = be_clipboard->Clear();
714
+ if (status == B_OK)
715
+ status = be_clipboard->Data()->AddData("text/plain", B_MIME_TYPE, text, strlen(text));
716
+ if (status == B_OK) status = be_clipboard->Commit();
717
+ be_clipboard->Unlock();
718
+ check(status, "Clipboard write");
719
+ return 0;
720
+ END_API(-1)
721
+ }
722
+
723
+ const char* ren_clipboard_get()
724
+ {
725
+ BEGIN_API
726
+ if (!be_clipboard->Lock()) throw std::runtime_error("Clipboard is busy");
727
+ const void* data = nullptr;
728
+ ssize_t size = 0;
729
+ status_t status = be_clipboard->Data()->FindData("text/plain", B_MIME_TYPE, &data, &size);
730
+ try {
731
+ result.assign(status == B_OK ? static_cast<const char*>(data) : "", status == B_OK ? size : 0);
732
+ } catch (...) {
733
+ be_clipboard->Unlock();
734
+ throw;
735
+ }
736
+ be_clipboard->Unlock();
737
+ return result.c_str();
738
+ END_API(nullptr)
739
+ }
740
+
741
+ int ren_launch(const char* url)
742
+ {
743
+ BEGIN_API
744
+ std::string scheme(url);
745
+ scheme.resize(scheme.find(':'));
746
+ for (char& c : scheme) if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
747
+ std::string mime = "application/x-vnd.Be.URL." + scheme;
748
+ const char* arguments[] = {url};
749
+ check(be_roster->Launch(mime.c_str(), 1, arguments), "BRoster::Launch");
750
+ return 0;
751
+ END_API(-1)
752
+ }
753
+
754
+ const char* ren_directory(int which)
755
+ {
756
+ BEGIN_API
757
+ if (which != 0) throw std::runtime_error("Unsupported directory");
758
+ BPath path;
759
+ check(find_directory(B_USER_SETTINGS_DIRECTORY, &path, true), "find_directory");
760
+ result = path.Path();
761
+ return result.c_str();
762
+ END_API(nullptr)
763
+ }
764
+ } // extern C