UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2023 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "pch.h" 30 31 #include "table.h" 32 #include "container.h" 33 #include "util.h" 34 #include "icons.h" 35 36 #include "../common/context.h" 37 #include "../common/object.h" 38 #include "../common/types.h" 39 40 #include <winrt/Microsoft.UI.Xaml.Data.h> 41 #include <winrt/Microsoft.UI.Xaml.Media.h> 42 #include <winrt/Microsoft.UI.Xaml.Input.h> 43 #include <winrt/Windows.UI.Core.h> 44 #include <winrt/Windows.ApplicationModel.h> 45 #include <winrt/Windows.ApplicationModel.DataTransfer.h> 46 47 using namespace winrt; 48 using namespace Microsoft::UI::Xaml; 49 using namespace Microsoft::UI::Xaml::Controls; 50 using namespace Windows::UI::Xaml::Interop; 51 using namespace winrt::Windows::Foundation; 52 using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives; 53 using namespace winrt::Microsoft::UI::Xaml::Media; 54 using namespace winrt::Windows::UI::Xaml::Input; 55 56 static UINT ui_double_click_time = GetDoubleClickTime(); 57 58 extern "C" void reg_table_destructor(UiContext * ctx, UiTable * table) { 59 // TODO: 60 } 61 62 static void textblock_set_str(TextBlock& t, const char* str) { 63 if (str) { 64 wchar_t* wstr = str2wstr(str, nullptr); 65 t.Text(winrt::hstring(wstr)); 66 free(wstr); 67 } 68 } 69 70 static void textblock_set_int(TextBlock& t, int i) { 71 wchar_t buf[16]; 72 swprintf(buf, 16, "%d"L, i); 73 t.Text(winrt::hstring(buf)); 74 } 75 76 UIEXPORT UIWIDGET ui_table_create(UiObject* obj, UiListArgs args) { 77 if (!args.model) { 78 return nullptr; 79 } 80 81 UiObject* current = uic_current_obj(obj); 82 83 // create widgets and wrapper obj 84 ScrollViewer scrollW = ScrollViewer(); 85 Grid grid = Grid(); 86 scrollW.Content(grid); 87 UiTable* uitable = new UiTable(obj, scrollW, grid); 88 reg_table_destructor(current->ctx, uitable); 89 90 uitable->getvalue = args.model->getvalue ? args.model->getvalue : args.getvalue; 91 uitable->onselection = args.onselection; 92 uitable->onselectiondata = args.onselectiondata; 93 uitable->onactivate = args.onactivate; 94 uitable->onactivatedata = args.onactivatedata; 95 uitable->ondragstart = args.ondragstart; 96 uitable->ondragstartdata = args.ondragstartdata; 97 uitable->ondragcomplete = args.ondragcomplete; 98 uitable->ondrop = args.ondrop; 99 uitable->ondropdata = args.ondropsdata; 100 101 // grid styling 102 winrt::Windows::UI::Color bg = { 255, 255, 255, 255 }; // test color 103 SolidColorBrush brush = SolidColorBrush(bg); 104 grid.Background(brush); 105 106 // add columns from args.model 107 uitable->add_header(args.model); 108 109 // bind var 110 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); 111 if (var) { 112 UiList* list = (UiList*)var->value; 113 list->update = ui_table_update; 114 list->getselection = ui_table_selection; 115 list->obj = uitable; 116 uitable->update(list, 0); 117 } 118 119 // create toolkit wrapper object and register destructor 120 UIElement elm = scrollW; 121 UiWidget* widget = new UiWidget(elm); 122 ui_context_add_widget_destructor(current->ctx, widget); 123 124 // add scrollW to current container 125 UI_APPLY_LAYOUT1(current, args); 126 127 current->container->Add(scrollW, false); 128 129 return widget; 130 } 131 132 extern "C" void ui_table_update(UiList * list, int i) { 133 UiTable* table = (UiTable*)list->obj; 134 table->clear(); 135 table->update(list, i); 136 } 137 138 extern "C" UiListSelection ui_table_selection(UiList * list) { 139 UiTable* table = (UiTable*)list->obj; 140 return table->uiselection(); 141 } 142 143 UiTable::UiTable(UiObject *obj, winrt::Microsoft::UI::Xaml::Controls::ScrollViewer scrollW, winrt::Microsoft::UI::Xaml::Controls::Grid grid) { 144 this->obj = obj; 145 146 this->scrollw = scrollw; 147 this->grid = grid; 148 149 winrt::Windows::UI::Color highlightBg = { 255, 234, 234, 234 }; 150 highlightBrush = SolidColorBrush(highlightBg); 151 152 winrt::Windows::UI::Color defaultBg = { 0, 0, 0, 0 }; // default 153 defaultBrush = SolidColorBrush(defaultBg); 154 155 winrt::Windows::UI::Color selectedBg = { 255, 204, 232, 255 }; // test color 156 selectedBrush = SolidColorBrush(selectedBg); 157 158 winrt::Windows::UI::Color selectedFg = { 255, 0, 90, 158 }; // test color 159 selectedBorderBrush = SolidColorBrush(selectedFg); 160 161 grid.KeyDown( 162 winrt::Microsoft::UI::Xaml::Input::KeyEventHandler( 163 [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& args) { 164 // key event for hanling the table cursor or enter 165 }) 166 ); 167 } 168 169 UiTable::~UiTable() { 170 ui_model_free(NULL, model); 171 } 172 173 void UiTable::add_header(UiModel* model) { 174 this->model = ui_model_copy(NULL, model); 175 176 GridLength gl; 177 gl.Value = 0; 178 gl.GridUnitType = GridUnitType::Auto; 179 180 // add header row definition 181 auto headerRowDef = RowDefinition(); 182 headerRowDef.Height(gl); 183 grid.RowDefinitions().Append(headerRowDef); 184 185 winrt::Windows::UI::Color borderColor = { 63, 0, 0, 0 }; 186 SolidColorBrush borderBrush = SolidColorBrush(borderColor); 187 188 189 for (int i = 0; i < model->columns;i++) { 190 char* title = model->titles[i]; 191 UiModelType type = model->types[i]; 192 193 // add grid column definition 194 auto colDef = ColumnDefinition(); 195 colDef.Width(gl); 196 grid.ColumnDefinitions().Append(colDef); 197 198 // header column border 199 Border headerBorder = Border(); 200 Thickness border = { 0,0,1,0 }; 201 headerBorder.BorderThickness(border); 202 headerBorder.BorderBrush(borderBrush); 203 204 // add text 205 auto hLabel = TextBlock(); 206 textblock_set_str(hLabel, title); 207 Thickness cellpadding = { 10,4,4,4 }; 208 hLabel.Padding(cellpadding); 209 hLabel.VerticalAlignment(VerticalAlignment::Stretch); 210 211 // event handler for highlighting and column resizing 212 headerBorder.PointerPressed( 213 winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( 214 [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { 215 // the last column doesn't need resize capabilities 216 if (i + 1 < model->columns) { 217 double width = headerBorder.ActualWidth(); 218 auto point = args.GetCurrentPoint(headerBorder); 219 auto position = point.Position(); 220 if (position.X + 4 >= width) { 221 this->resize = true; 222 this->resizedCol = headerBorder; 223 } 224 } 225 }) 226 ); 227 headerBorder.PointerReleased( 228 winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( 229 [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { 230 this->resize = false; 231 }) 232 ); 233 headerBorder.PointerMoved( 234 winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( 235 [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { 236 if (this->resize) { 237 auto point = args.GetCurrentPoint(this->resizedCol); 238 auto position = point.Position(); 239 if (position.X > 1) { 240 this->resizedCol.Width(position.X); 241 } 242 } 243 }) 244 ); 245 headerBorder.PointerEntered( 246 winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( 247 [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { 248 // TODO: background 249 }) 250 ); 251 headerBorder.PointerExited( 252 winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( 253 [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { 254 // TODO: background 255 }) 256 ); 257 258 259 260 // add controls 261 headerBorder.Child(hLabel); 262 263 grid.SetColumn(headerBorder, i); 264 grid.SetRow(headerBorder, 0); 265 grid.Children().Append(headerBorder); 266 267 UiTableColumn h; 268 h.header = headerBorder; 269 header.push_back(h); 270 } 271 272 maxrows = 1; 273 } 274 275 static ULONG64 getsystime() { 276 SYSTEMTIME st; 277 GetSystemTime(&st); 278 return st.wYear * 10000000000000 + 279 st.wMonth * 100000000000 + 280 st.wDay * 1000000000 + 281 st.wHour * 10000000 + 282 st.wMinute * 100000 + 283 st.wSecond * 1000 + 284 st.wMilliseconds; 285 } 286 287 void UiTable::update(UiList* list, int i) { 288 if (getvalue == nullptr) { 289 return; 290 } 291 292 Thickness b1 = { 1, 1, 0, 1 }; // first col 293 Thickness b2 = { 0, 1, 0, 1 }; // middle 294 Thickness b3 = { 0, 1, 1, 1 }; // last col 295 296 GridLength gl; 297 gl.Value = 0; 298 gl.GridUnitType = GridUnitType::Auto; 299 300 // iterate model 301 int row = 1; 302 void* elm = list->first(list); 303 while (elm) { 304 if (row >= maxrows) { 305 auto rowdef = RowDefinition(); 306 rowdef.Height(gl); 307 grid.RowDefinitions().Append(rowdef); 308 maxrows = row; 309 } 310 311 Thickness cellpadding = { 10,0,4,0 }; 312 313 // model column, usually the same as col, however UI_ICON_TEXT uses two columns in the model 314 int model_col = 0; 315 for (int col = 0; col < header.size(); col++, model_col++) { 316 // create ui elements with the correct cell border 317 // dependeing on the column 318 Border cellBorder = Border(); 319 cellBorder.Background(defaultBrush); 320 cellBorder.BorderBrush(defaultBrush); 321 if (col == 0) { 322 cellBorder.BorderThickness(b1); 323 } 324 else if (col + 1 == header.size()) { 325 cellBorder.BorderThickness(b3); 326 } 327 else { 328 cellBorder.BorderThickness(b2); 329 } 330 331 // dnd 332 if (ondragstart) { 333 cellBorder.CanDrag(true); 334 cellBorder.DragStarting([this](IInspectable const& sender, DragStartingEventArgs args) { 335 UiDnD dnd; 336 dnd.evttype = 0; 337 dnd.dndstartargs = args; 338 dnd.dndcompletedargs = { nullptr }; 339 dnd.drageventargs = { nullptr }; 340 dnd.data = args.Data(); 341 342 UiEvent evt; 343 evt.obj = this->obj; 344 evt.window = evt.obj->window; 345 evt.document = obj->ctx->document; 346 evt.eventdata = &dnd; 347 evt.intval = 0; 348 349 this->ondragstart(&evt, this->ondragstartdata); 350 }); 351 cellBorder.DropCompleted([this](IInspectable const& sender, DropCompletedEventArgs args) { 352 UiDnD dnd; 353 dnd.evttype = 1; 354 dnd.dndstartargs = { nullptr }; 355 dnd.dndcompletedargs = args; 356 dnd.drageventargs = { nullptr }; 357 dnd.data = { nullptr }; 358 359 UiEvent evt; 360 evt.obj = this->obj; 361 evt.window = evt.obj->window; 362 evt.document = obj->ctx->document; 363 evt.eventdata = &dnd; 364 evt.intval = 0; 365 366 if (this->ondragcomplete) { 367 this->ondragcomplete(&evt, this->ondragcompletedata); 368 } 369 }); 370 } 371 if (ondrop) { 372 cellBorder.AllowDrop(true); 373 cellBorder.Drop(DragEventHandler([this](winrt::Windows::Foundation::IInspectable const& sender, DragEventArgs const& args){ 374 UiDnD dnd; 375 dnd.evttype = 2; 376 dnd.dndstartargs = { nullptr }; 377 dnd.dndcompletedargs = { nullptr }; 378 dnd.drageventargs = args; 379 dnd.dataview = args.DataView(); 380 381 UiEvent evt; 382 evt.obj = this->obj; 383 evt.window = evt.obj->window; 384 evt.document = obj->ctx->document; 385 evt.eventdata = &dnd; 386 evt.intval = 0; 387 388 this->ondrop(&evt, this->ondropdata); 389 })); 390 cellBorder.DragOver(DragEventHandler([this](winrt::Windows::Foundation::IInspectable const& sender, DragEventArgs const& args){ 391 args.AcceptedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::Copy); 392 })); 393 } 394 395 // set the cell value 396 // depending on the type, we create different cell controls 397 UiModelType type = model->types[col]; 398 switch (type) { 399 case UI_STRING_FREE: 400 case UI_STRING: { 401 TextBlock cell = TextBlock(); 402 cell.Padding(cellpadding); 403 cell.VerticalAlignment(VerticalAlignment::Stretch); 404 char *val = (char*)getvalue(elm, model_col); 405 textblock_set_str(cell, val); 406 cellBorder.Child(cell); 407 if (type == UI_STRING_FREE && val) { 408 free(val); 409 } 410 411 break; 412 } 413 case UI_INTEGER: { 414 TextBlock cell = TextBlock(); 415 cell.Padding(cellpadding); 416 cell.VerticalAlignment(VerticalAlignment::Stretch); 417 int *value = (int*)getvalue(elm, model_col); 418 if (value) { 419 textblock_set_int(cell, *value); 420 } 421 cellBorder.Child(cell); 422 break; 423 } 424 case UI_ICON: { 425 UiIcon* iconConstr = (UiIcon*)getvalue(elm, model_col); 426 if (iconConstr) { 427 IconElement icon = iconConstr->getIcon(); 428 cellBorder.Child(icon); 429 } 430 break; 431 } 432 case UI_ICON_TEXT_FREE: 433 case UI_ICON_TEXT: { 434 StackPanel cellPanel = StackPanel(); 435 cellPanel.Spacing(2); 436 cellPanel.Padding(cellpadding); 437 cellPanel.VerticalAlignment(VerticalAlignment::Stretch); 438 439 cellPanel.Orientation(Orientation::Horizontal); 440 UiIcon* iconConstr = (UiIcon*)getvalue(elm, model_col++); 441 char* str = (char*)getvalue(elm, model_col); 442 if (iconConstr) { 443 IconElement icon = iconConstr->getIcon(); 444 cellPanel.Children().Append(icon); 445 } 446 TextBlock cell = TextBlock(); 447 textblock_set_str(cell, str); 448 cellPanel.Children().Append(cell); 449 cellBorder.Child(cellPanel); 450 if (type == UI_ICON_TEXT_FREE && str) { 451 free(str); 452 } 453 break; 454 } 455 } 456 457 // event handler 458 cellBorder.PointerPressed( 459 winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( 460 [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { 461 winrt::Windows::System::VirtualKeyModifiers modifiers = args.KeyModifiers(); 462 bool update_selection = true; 463 464 if (modifiers == winrt::Windows::System::VirtualKeyModifiers::Control) { 465 // add/remove current row 466 if (!is_row_selected(row)) { 467 row_background(row, selectedBrush, selectedBorderBrush); 468 selection.push_back(row); 469 } 470 else { 471 row_background(row, highlightBrush, highlightBrush); 472 remove_from_selection(row); 473 } 474 } 475 else if (modifiers == winrt::Windows::System::VirtualKeyModifiers::None || selection.size() == 0) { 476 // no modifier or shift is pressed but there is no selection 477 if (selection.size() > 0) { 478 change_rows_bg(selection, defaultBrush, defaultBrush); 479 } 480 481 row_background(row, selectedBrush, selectedBorderBrush); 482 selection = { row }; 483 if (modifiers == winrt::Windows::System::VirtualKeyModifiers::None) { 484 SYSTEMTIME st; 485 GetSystemTime(&st); 486 487 ULONG64 now = getsystime(); 488 ULONG64 tdiff = now - lastPointerPress; 489 if (tdiff < ui_double_click_time && onactivate != nullptr) { 490 // two pointer presse events in short time and we have an onactivate handler 491 update_selection = false; // we don't want an additional selection event 492 lastPointerPress = 0; // reset double-click 493 494 int selectedrow = row - 1; // subtract header row 495 496 UiListSelection selection; 497 selection.count = 1; 498 selection.rows = &selectedrow; 499 500 UiEvent evt; 501 evt.obj = obj; 502 evt.window = obj->window; 503 evt.document = obj->ctx->document; 504 evt.eventdata = &selection; 505 evt.intval = selectedrow; 506 onactivate(&evt, onactivatedata); 507 } 508 else { 509 lastPointerPress = now; 510 } 511 } 512 } 513 else if (modifiers == winrt::Windows::System::VirtualKeyModifiers::Shift) { 514 // select everything between the first selection and the current row 515 std::sort(selection.begin(), selection.end()); 516 int first = selection.front(); 517 int last = row; 518 if (first > row) { 519 last = first; 520 first = row; 521 } 522 523 // clear previous selection 524 change_rows_bg(selection, defaultBrush, defaultBrush); 525 526 // create new selection 527 std::vector<int> newselection; 528 for (int s = first; s <= last; s++) { 529 newselection.push_back(s); 530 } 531 selection = newselection; 532 change_rows_bg(selection, selectedBrush, selectedBorderBrush); 533 } 534 535 if (update_selection) { 536 call_handler(onselection, onselectiondata); 537 } 538 }) 539 ); 540 cellBorder.PointerReleased( 541 winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( 542 [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { 543 544 }) 545 ); 546 cellBorder.PointerEntered( 547 winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( 548 [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { 549 if (!is_row_selected(row)) { 550 row_background(row, highlightBrush, highlightBrush); 551 } 552 }) 553 ); 554 cellBorder.PointerExited( 555 winrt::Microsoft::UI::Xaml::Input::PointerEventHandler( 556 [=](IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args) { 557 if (!is_row_selected(row)) { 558 row_background(row, defaultBrush, defaultBrush); 559 } 560 }) 561 ); 562 563 grid.SetColumn(cellBorder, col); 564 grid.SetRow(cellBorder, row); 565 grid.Children().Append(cellBorder); 566 } 567 568 row++; 569 elm = list->next(list); 570 } 571 } 572 573 void UiTable::clear() { 574 for (int i = grid.Children().Size()-1; i >= 0; i--) { 575 FrameworkElement elm = grid.Children().GetAt(i).as<FrameworkElement>(); 576 int child_row = grid.GetRow(elm); 577 if (child_row > 0) { 578 grid.Children().RemoveAt(i); 579 } 580 } 581 582 // TODO: should we clean row definitions? 583 } 584 585 void UiTable::row_background(int row, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush) { 586 Thickness b1 = { 1, 1, 0, 1 }; // first col 587 Thickness b2 = { 0, 1, 0, 1 }; // middle 588 Thickness b3 = { 0, 1, 1, 1 }; // last col 589 590 for (auto child : grid.Children()) { 591 FrameworkElement elm = child.as<FrameworkElement>(); 592 int child_row = grid.GetRow(elm); 593 if (child_row == row) { 594 Border b = elm.as<Border>(); 595 b.Background(brush); 596 b.BorderBrush(borderBrush); 597 } 598 } 599 } 600 601 void UiTable::change_rows_bg(std::vector<int> rows, winrt::Microsoft::UI::Xaml::Media::Brush brush, winrt::Microsoft::UI::Xaml::Media::Brush borderBrush) { 602 std::for_each(rows.cbegin(), rows.cend(), [&](const int& row) {row_background(row, brush, borderBrush); }); 603 } 604 605 bool UiTable::is_row_selected(int row) { 606 return std::find(selection.begin(), selection.end(), row) != selection.end() ? true : false; 607 } 608 609 void UiTable::remove_from_selection(int row) { 610 selection.erase(std::remove(selection.begin(), selection.end(), row), selection.end()); 611 selection.shrink_to_fit(); 612 } 613 614 UiListSelection UiTable::uiselection() { 615 std::sort(selection.begin(), selection.end()); 616 617 UiListSelection selobj; 618 selobj.count = selection.size(); 619 selobj.rows = nullptr; 620 if (selobj.count > 0) { 621 selobj.rows = (int*)calloc(selobj.count, sizeof(int)); 622 memcpy(selobj.rows, selection.data(), selobj.count * sizeof(int)); 623 for (int i = 0; i < selobj.count; i++) { 624 selobj.rows[i]--; 625 } 626 } 627 return selobj; 628 } 629 630 void UiTable::call_handler(ui_callback cb, void* cbdata) { 631 if (!cb) { 632 return; 633 } 634 635 UiListSelection selobj = uiselection(); 636 637 UiEvent evt; 638 evt.obj = obj; 639 evt.window = obj->window; 640 evt.document = obj->ctx->document; 641 evt.eventdata = &selobj; 642 evt.intval = 0; 643 cb(&evt, cbdata); 644 645 if (selobj.rows) { 646 free(selobj.rows); 647 } 648 } 649