1/* 2 * This file is part of gitg 3 * 4 * Copyright (C) 2012 - Jesse van den Kieboom 5 * 6 * gitg is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * gitg is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with gitg. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20namespace GitgHistory 21{ 22 public enum DefaultSelection 23 { 24 CURRENT_BRANCH, 25 ALL_BRANCHES, 26 ALL_COMMITS 27 } 28 29 /* The main history view. This view shows the equivalent of git log, but 30 * in a nice way with lanes, merges, ref labels etc. 31 */ 32 public class Activity : Object, GitgExt.UIElement, GitgExt.Activity, GitgExt.Searchable, GitgExt.History 33 { 34 // Do this to pull in config.h before glib.h (for gettext...) 35 private const string version = Gitg.Config.VERSION; 36 37 public GitgExt.Application? application { owned get; construct set; } 38 39 private Gitg.CommitModel? d_commit_list_model; 40 41 private Gee.HashSet<Ggit.OId> d_selected; 42 private Ggit.OId? d_scroll_to; 43 private float d_scroll_y; 44 private ulong d_insertsig; 45 private Settings d_settings; 46 private uint d_walker_update_idle_id; 47 private ulong d_refs_list_selection_id; 48 private ulong d_refs_list_changed_id; 49 private ulong d_externally_changed_id; 50 private ulong d_commits_changed_id; 51 52 private Gitg.WhenMapped? d_reload_when_mapped; 53 54 private Paned d_main; 55 private Gitg.PopupMenu d_refs_list_popup; 56 private Gitg.PopupMenu d_commit_list_popup; 57 58 private string[] d_mainline; 59 private bool d_ignore_external; 60 61 private Gitg.UIElements<GitgExt.HistoryPanel> d_panels; 62 63 public Activity(GitgExt.Application application) 64 { 65 Object(application: application); 66 } 67 68 public string id 69 { 70 owned get { return "/org/gnome/gitg/Activities/History"; } 71 } 72 73 private Gitg.Repository d_repository; 74 75 public Gitg.Repository repository 76 { 77 get 78 { 79 return d_repository; 80 } 81 82 set 83 { 84 if (d_repository != value) 85 { 86 d_repository = value; 87 reload(); 88 } 89 } 90 } 91 92 public void foreach_selected(GitgExt.ForeachCommitSelectionFunc func) 93 { 94 bool breakit = false; 95 96 d_main.commit_list_view.get_selection().selected_foreach((model, path, iter) => { 97 if (!breakit) 98 { 99 var c = d_commit_list_model.commit_from_iter(iter); 100 101 if (c != null) 102 { 103 breakit = !func(c); 104 } 105 } 106 }); 107 } 108 109 public void select(Gitg.Commit commit) 110 { 111 var model = (Gitg.CommitModel)d_main.commit_list_view.model; 112 var path = model.path_from_commit(commit); 113 114 if (path != null) 115 { 116 var sel = d_main.commit_list_view.get_selection(); 117 sel.select_path(path); 118 119 d_main.commit_list_view.scroll_to_cell(path, null, true, 0.5f, 0); 120 } 121 else 122 { 123 stderr.printf("Failed to lookup tree path for commit '%s'\n", commit.get_id().to_string()); 124 } 125 } 126 127 construct 128 { 129 d_settings = new Settings(Gitg.Config.APPLICATION_ID + ".preferences.history"); 130 131 d_settings.changed["topological-order"].connect((s, k) => { 132 update_sort_mode(); 133 }); 134 135 d_settings.changed["mainline-head"].connect((s, k) => { 136 update_walker(); 137 }); 138 139 d_settings.changed["show-upstream-with-branch"].connect((s, k) => { 140 update_walker(); 141 }); 142 143 d_selected = new Gee.HashSet<Ggit.OId>((Gee.HashDataFunc<Ggit.OId>)Ggit.OId.hash, 144 (Gee.EqualDataFunc<Ggit.OId>)Ggit.OId.equal); 145 146 d_commit_list_model = new Gitg.CommitModel(application.repository); 147 d_commit_list_model.started.connect(on_commit_model_started); 148 d_commit_list_model.finished.connect(on_commit_model_finished); 149 150 update_sort_mode(); 151 152 d_repository = application.repository; 153 154 application.bind_property("repository", this, 155 "repository", BindingFlags.DEFAULT); 156 157 reload_mainline(); 158 159 d_externally_changed_id = application.repository_changed_externally.connect(repository_changed_externally); 160 d_commits_changed_id = application.repository_commits_changed.connect(repository_commits_changed); 161 } 162 163 private void repository_changed_externally(GitgExt.ExternalChangeHint hint) 164 { 165 if (d_main != null && (hint & GitgExt.ExternalChangeHint.REFS) != 0 && !d_ignore_external) 166 { 167 reload_when_mapped(); 168 } 169 170 d_ignore_external = false; 171 } 172 173 private void repository_commits_changed() 174 { 175 if (d_main != null) 176 { 177 d_ignore_external = true; 178 reload_when_mapped(); 179 } 180 } 181 182 private void reload_when_mapped() 183 { 184 if (d_main != null) 185 { 186 d_reload_when_mapped = new Gitg.WhenMapped(d_main); 187 188 d_reload_when_mapped.update(() => { 189 reload(); 190 }, this); 191 } 192 } 193 194 public override void dispose() 195 { 196 if (d_refs_list_selection_id != 0) 197 { 198 d_main.refs_list.disconnect(d_refs_list_selection_id); 199 d_refs_list_selection_id = 0; 200 } 201 202 if (d_refs_list_changed_id != 0) 203 { 204 d_main.refs_list.disconnect(d_refs_list_changed_id); 205 d_refs_list_changed_id = 0; 206 } 207 208 if (d_walker_update_idle_id != 0) 209 { 210 Source.remove(d_walker_update_idle_id); 211 d_walker_update_idle_id = 0; 212 } 213 214 if (d_externally_changed_id != 0) 215 { 216 application.disconnect(d_externally_changed_id); 217 d_externally_changed_id = 0; 218 } 219 220 if (d_commits_changed_id != 0) 221 { 222 application.disconnect(d_commits_changed_id); 223 d_commits_changed_id = 0; 224 } 225 226 d_commit_list_model.repository = null; 227 base.dispose(); 228 } 229 230 private void update_sort_mode() 231 { 232 if (d_settings.get_boolean("topological-order")) 233 { 234 d_commit_list_model.sort_mode = Ggit.SortMode.TOPOLOGICAL; 235 } 236 else 237 { 238 d_commit_list_model.sort_mode = Ggit.SortMode.TIME | Ggit.SortMode.TOPOLOGICAL; 239 } 240 } 241 242 private void on_commit_model_started(Gitg.CommitModel model) 243 { 244 if (d_insertsig == 0) 245 { 246 d_insertsig = d_commit_list_model.row_inserted.connect(on_row_inserted_select); 247 } 248 } 249 250 private void on_row_inserted_select(Gtk.TreeModel model, Gtk.TreePath path, Gtk.TreeIter iter) 251 { 252 var commit = d_commit_list_model.commit_from_path(path); 253 254 var sel = d_main.commit_list_view.get_selection(); 255 256 if (d_selected.size == 0 || d_selected.remove(commit.get_id())) 257 { 258 sel.select_path(path); 259 260 if (commit.get_id().equal(d_scroll_to)) 261 { 262 d_main.commit_list_view.scroll_to_cell(path, 263 null, 264 true, 265 d_scroll_y, 266 0); 267 268 d_scroll_to = null; 269 } 270 } 271 272 if (d_selected.size == 0 || (sel.count_selected_rows() != 0 && 273 (sel.mode == Gtk.SelectionMode.SINGLE || 274 sel.mode == Gtk.SelectionMode.BROWSE))) 275 { 276 d_selected.clear(); 277 278 d_commit_list_model.disconnect(d_insertsig); 279 d_insertsig = 0; 280 } 281 } 282 283 private void scroll_into_view() 284 { 285 if (d_main == null) 286 { 287 return; 288 } 289 290 var sel = d_main.commit_list_view.get_selection(); 291 292 Gtk.TreeModel m; 293 var rows = sel.get_selected_rows(out m); 294 295 if (rows == null) 296 { 297 return; 298 } 299 300 var row = rows.data; 301 302 Gtk.TreePath startp; 303 Gtk.TreePath endp; 304 305 if (d_main.commit_list_view.get_visible_range(out startp, out endp)) 306 { 307 if (row.compare(startp) < 0 || row.compare(endp) > 0) 308 { 309 d_main.commit_list_view.scroll_to_cell(row, null, true, 0, 0); 310 } 311 } 312 } 313 314 private void on_commit_model_finished(Gitg.CommitModel model) 315 { 316 if (d_insertsig != 0) 317 { 318 d_commit_list_model.disconnect(d_insertsig); 319 d_insertsig = 0; 320 } 321 322 scroll_into_view(); 323 } 324 325 private void on_commit_model_begin_clear(Gitg.CommitModel model) 326 { 327 d_main.commit_list_view.model = null; 328 } 329 330 private void on_commit_model_end_clear(Gitg.CommitModel model) 331 { 332 d_main.commit_list_view.model = d_commit_list_model; 333 } 334 335 public bool available 336 { 337 get { return true; } 338 } 339 340 public string display_name 341 { 342 owned get { return _("History"); } 343 } 344 345 public string description 346 { 347 owned get { return _("Examine the history of the repository"); } 348 } 349 350 public string? icon 351 { 352 owned get { return "view-list-symbolic"; } 353 } 354 355 public Gtk.Widget? widget 356 { 357 owned get 358 { 359 if (d_main == null) 360 { 361 build_ui(); 362 } 363 364 return d_main; 365 } 366 } 367 368 public bool is_default_for(string action) 369 { 370 return (action == "" || action == "history"); 371 } 372 373 public bool enabled 374 { 375 get { return true; } 376 } 377 378 public int negotiate_order(GitgExt.UIElement other) 379 { 380 return -1; 381 } 382 383 private void store_changed_mainline() 384 { 385 var repo = application.repository; 386 387 if (repo == null) 388 { 389 return; 390 } 391 392 Ggit.Config config; 393 394 try 395 { 396 config = repo.get_config(); 397 } catch { return; } 398 399 store_mainline(config, string.joinv(",", d_mainline)); 400 } 401 402 private void store_mainline(Ggit.Config? config, string mainline) 403 { 404 if (config != null) 405 { 406 try 407 { 408 config.set_string("gitg.mainline", mainline); 409 } 410 catch (Error e) 411 { 412 stderr.printf("Failed to set gitg.mainline: %s\n", e.message); 413 } 414 } 415 } 416 417 private void reload_mainline() 418 { 419 d_reload_when_mapped = null; 420 421 var uniq = new Gee.HashSet<string>(); 422 423 d_mainline = new string[0]; 424 425 var repository = application.repository; 426 427 if (repository == null) 428 { 429 return; 430 } 431 432 Ggit.Config? config = null; 433 var ref_names = new string[0]; 434 435 try 436 { 437 config = repository.get_config(); 438 ref_names = config.snapshot().get_string("gitg.mainline").split(","); 439 } 440 catch 441 { 442 ref_names = new string[] {"refs/heads/master"}; 443 } 444 445 foreach (var name in ref_names) 446 { 447 Gitg.Ref r; 448 449 try 450 { 451 r = repository.lookup_reference(name); 452 } 453 catch (Error e) 454 { 455 stderr.printf("Failed to lookup reference (%s): %s\n", name, e.message); 456 continue; 457 } 458 459 var id = id_for_ref(r); 460 461 if (id != null && uniq.add(name)) 462 { 463 d_mainline += name; 464 } 465 } 466 467 store_mainline(config, string.joinv(",", d_mainline)); 468 } 469 470 public RefsList refs_list 471 { 472 get { return d_main.refs_list; } 473 } 474 475 private void reload() 476 { 477 if (d_walker_update_idle_id != 0) 478 { 479 Source.remove(d_walker_update_idle_id); 480 d_walker_update_idle_id = 0; 481 } 482 483 var view = d_main.commit_list_view; 484 485 double vadj = d_main.refs_list.get_adjustment().get_value(); 486 487 reload_mainline(); 488 489 d_selected.clear(); 490 491 d_scroll_to = null; 492 493 Gtk.TreePath startp, endp; 494 495 var isvis = view.get_visible_range(out startp, out endp); 496 497 view.get_selection().selected_foreach((model, path, iter) => { 498 var c = d_commit_list_model.commit_from_iter(iter); 499 500 if (c != null) 501 { 502 d_selected.add(c.get_id()); 503 504 if (d_scroll_to == null && 505 (!isvis || startp.compare(path) <= 0 && endp.compare(path) >= 0)) 506 { 507 if (isvis) 508 { 509 Gdk.Rectangle rect; 510 Gdk.Rectangle visrect; 511 512 view.get_cell_area(path, null, out rect); 513 view.get_visible_rect(out visrect); 514 515 int x, y; 516 517 view.convert_tree_to_bin_window_coords(visrect.x, 518 visrect.y, 519 out x, 520 out y); 521 522 // + 2 seems to work correctly here, but this is probably 523 // something related to a border or padding of the 524 // treeview (i.e. theme related) 525 d_scroll_y = (float)(rect.y + rect.height / 2.0 - y + 2) / (float)visrect.height; 526 } 527 else 528 { 529 d_scroll_y = 0.5f; 530 } 531 532 d_scroll_to = c.get_id(); 533 } 534 } 535 }); 536 537 // Clears the commit model 538 d_commit_list_model.repository = repository; 539 540 // Reloads branches, tags, etc. 541 d_main.refs_list.repository = repository; 542 543 ulong sid = 0; 544 545 sid = d_main.refs_list.size_allocate.connect((a) => { 546 d_main.refs_list.get_adjustment().set_value(vadj); 547 548 if (sid != 0) 549 { 550 d_main.refs_list.disconnect(sid); 551 } 552 }); 553 } 554 555 private void build_ui() 556 { 557 d_main = new Paned(); 558 559 d_main.refs_list.remote_lookup = application.remote_lookup; 560 561 d_main.commit_list_view.model = d_commit_list_model; 562 563 d_main.commit_list_view.get_selection().changed.connect((sel) => { 564 selection_changed(); 565 566 // Set primary selection to sha1 of first selected commit 567 var clip = ((Gtk.Widget)application).get_clipboard(Gdk.SELECTION_PRIMARY); 568 569 foreach_selected((commit) => { 570 clip.set_text(commit.get_id().to_string(), -1); 571 return false; 572 }); 573 }); 574 575 var engine = Gitg.PluginsEngine.get_default(); 576 577 var extset = new Peas.ExtensionSet(engine, 578 typeof(GitgExt.HistoryPanel), 579 "history", 580 this, 581 "application", 582 application); 583 584 d_panels = new Gitg.UIElements<GitgExt.HistoryPanel>(extset, 585 d_main.stack_panel); 586 587 d_refs_list_popup = new Gitg.PopupMenu(d_main.refs_list); 588 d_refs_list_popup.populate_menu.connect(on_refs_list_populate_menu); 589 590 d_refs_list_selection_id = d_main.refs_list.notify["selection"].connect(update_walker_idle); 591 d_refs_list_changed_id = d_main.refs_list.changed.connect(update_walker_idle); 592 593 d_commit_list_popup = new Gitg.PopupMenu(d_main.commit_list_view); 594 d_commit_list_popup.populate_menu.connect(on_commit_list_populate_menu); 595 d_commit_list_popup.request_menu_position.connect(on_commit_list_request_menu_position); 596 597 application.bind_property("repository", d_main.refs_list, 598 "repository", 599 BindingFlags.DEFAULT | 600 BindingFlags.SYNC_CREATE); 601 602 d_main.commit_list_view.set_search_equal_func(search_filter_func); 603 604 d_commit_list_model.begin_clear.connect(on_commit_model_begin_clear); 605 d_commit_list_model.end_clear.connect(on_commit_model_end_clear); 606 } 607 608 private void update_walker_idle() 609 { 610 if (d_repository == null) 611 { 612 return; 613 } 614 615 if (d_walker_update_idle_id == 0) 616 { 617 d_walker_update_idle_id = Idle.add(() => { 618 d_walker_update_idle_id = 0; 619 update_walker(); 620 return false; 621 }); 622 } 623 } 624 625 private Gtk.Menu? popup_on_ref(Gdk.EventButton? event) 626 { 627 int cell_x; 628 int cell_y; 629 int cell_w; 630 Gtk.TreePath path; 631 Gtk.TreeViewColumn column; 632 633 if (event == null) 634 { 635 return null; 636 } 637 638 if (!d_main.commit_list_view.get_path_at_pos((int)event.x, 639 (int)event.y, 640 out path, 641 out column, 642 out cell_x, 643 out cell_y)) 644 { 645 return null; 646 } 647 648 var cell = d_main.commit_list_view.find_cell_at_pos(column, path, cell_x, out cell_w) as Gitg.CellRendererLanes; 649 650 if (cell == null) 651 { 652 return null; 653 } 654 655 var reference = cell.get_ref_at_pos(d_main.commit_list_view, cell_x, cell_w, null); 656 657 if (reference == null) 658 { 659 return null; 660 } 661 662 return popup_menu_for_ref(reference); 663 } 664 665 private Gtk.Menu? on_commit_list_populate_menu(Gdk.EventButton? event) 666 { 667 var ret = popup_on_ref(event); 668 669 if (ret == null) 670 { 671 ret = popup_menu_for_commit(event); 672 } 673 674 // event is most likely null. 675 if (ret == null) 676 { 677 ret = popup_menu_for_selection(); 678 } 679 680 return ret; 681 } 682 683 private Gdk.Rectangle? on_commit_list_request_menu_position() 684 { 685 var selection = d_main.commit_list_view.get_selection(); 686 687 Gtk.TreeModel model; 688 Gtk.TreeIter iter; 689 690 if (!selection.get_selected(out model, out iter)) 691 { 692 return null; 693 } 694 695 var path = model.get_path(iter); 696 697 Gdk.Rectangle rect = { 0 }; 698 699 d_main.commit_list_view.get_cell_area(path, null, out rect); 700 d_main.commit_list_view.convert_bin_window_to_widget_coords(rect.x, rect.y, 701 out rect.x, out rect.y); 702 703 return rect; 704 } 705 706 private void add_ref_action(Gee.LinkedList<GitgExt.RefAction> actions, 707 GitgExt.RefAction? action) 708 { 709 if (action != null && action.available) 710 { 711 actions.add(action); 712 } 713 } 714 715 private Gtk.Menu? populate_menu_for_commit(Gitg.Commit commit) 716 { 717 var af = new ActionInterface(application, d_main.refs_list); 718 719 af.updated.connect(() => { 720 d_ignore_external = true; 721 }); 722 723 var actions = new Gee.LinkedList<GitgExt.CommitAction>(); 724 725 add_commit_action(actions, 726 new Gitg.CommitActionCreateBranch(application, 727 af, 728 commit)); 729 730 add_commit_action(actions, 731 new Gitg.CommitActionCreateTag(application, 732 af, 733 commit)); 734 735 add_commit_action(actions, 736 new Gitg.CommitActionCreatePatch(application, 737 af, 738 commit)); 739 740 add_commit_action(actions, 741 new Gitg.CommitActionCherryPick(application, 742 af, 743 commit)); 744 745 var exts = new Peas.ExtensionSet(Gitg.PluginsEngine.get_default(), 746 typeof(GitgExt.CommitAction), 747 "application", 748 application, 749 "action_interface", 750 af, 751 "commit", 752 commit); 753 754 exts.foreach((extset, info, extension) => { 755 add_commit_action(actions, extension as GitgExt.CommitAction); 756 }); 757 758 if (actions.size == 0) 759 { 760 return null; 761 } 762 763 Gtk.Menu menu = new Gtk.Menu(); 764 765 foreach (var ac in actions) 766 { 767 ac.populate_menu(menu); 768 } 769 770 // To keep actions alive as long as the menu is alive 771 menu.set_data("gitg-ext-actions", actions); 772 773 return menu; 774 } 775 776 private Gtk.Menu? popup_menu_for_selection() 777 { 778 var selection = d_main.commit_list_view.get_selection(); 779 780 Gtk.TreeIter iter; 781 782 if (!selection.get_selected(null, out iter)) 783 { 784 return null; 785 } 786 787 var commit = d_commit_list_model.commit_from_iter(iter); 788 789 if (commit == null) 790 { 791 return null; 792 } 793 794 return populate_menu_for_commit(commit); 795 } 796 797 private Gtk.Menu? popup_menu_for_commit(Gdk.EventButton? event) 798 { 799 int cell_x; 800 int cell_y; 801 Gtk.TreePath path; 802 Gtk.TreeViewColumn column; 803 804 if (event == null) 805 { 806 return null; 807 } 808 809 if (!d_main.commit_list_view.get_path_at_pos((int)event.x, 810 (int)event.y, 811 out path, 812 out column, 813 out cell_x, 814 out cell_y)) 815 { 816 return null; 817 } 818 819 var commit = d_commit_list_model.commit_from_path(path); 820 821 if (commit == null) 822 { 823 return null; 824 } 825 826 d_main.commit_list_view.get_selection().select_path(path); 827 828 return populate_menu_for_commit(commit); 829 } 830 831 private Gtk.Menu? popup_menu_for_ref(Gitg.Ref reference) 832 { 833 var actions = new Gee.LinkedList<GitgExt.RefAction?>(); 834 835 var af = new ActionInterface(application, d_main.refs_list); 836 837 af.updated.connect(() => { 838 d_ignore_external = true; 839 }); 840 841 add_ref_action(actions, new Gitg.RefActionCheckout(application, af, reference)); 842 add_ref_action(actions, new Gitg.RefActionRename(application, af, reference)); 843 add_ref_action(actions, new Gitg.RefActionDelete(application, af, reference)); 844 add_ref_action(actions, new Gitg.RefActionCopyName(application, af, reference)); 845 846 var fetch = new Gitg.RefActionFetch(application, af, reference); 847 848 if (fetch.available) 849 { 850 actions.add(null); 851 } 852 853 add_ref_action(actions, fetch); 854 855 var push = new Gitg.RefActionPush(application, af, reference); 856 857 if (push.available) 858 { 859 actions.add(null); 860 } 861 862 add_ref_action(actions, push); 863 864 var merge = new Gitg.RefActionMerge(application, af, reference); 865 866 if (merge.available) 867 { 868 actions.add(null); 869 add_ref_action(actions, merge); 870 } 871 872 var exts = new Peas.ExtensionSet(Gitg.PluginsEngine.get_default(), 873 typeof(GitgExt.RefAction), 874 "application", 875 application, 876 "action_interface", 877 af, 878 "reference", 879 reference); 880 881 var addedsep = false; 882 883 exts.foreach((extset, info, extension) => { 884 if (!addedsep) 885 { 886 actions.add(null); 887 addedsep = true; 888 } 889 890 add_ref_action(actions, extension as GitgExt.RefAction); 891 }); 892 893 if (actions.is_empty) 894 { 895 return null; 896 } 897 898 Gtk.Menu menu = new Gtk.Menu(); 899 900 foreach (var ac in actions) 901 { 902 if (ac != null) 903 { 904 ac.populate_menu(menu); 905 } 906 else 907 { 908 var sep = new Gtk.SeparatorMenuItem(); 909 sep.show(); 910 menu.append(sep); 911 } 912 } 913 914 var sep = new Gtk.SeparatorMenuItem(); 915 sep.show(); 916 menu.append(sep); 917 918 var item = new Gtk.CheckMenuItem.with_label(_("Mainline")); 919 int pos = 0; 920 921 foreach (var ml in d_mainline) 922 { 923 if (ml == reference.get_name()) 924 { 925 item.active = true; 926 break; 927 } 928 929 ++pos; 930 } 931 932 item.activate.connect(() => { 933 if (item.active) 934 { 935 d_mainline += reference.get_name(); 936 } 937 else 938 { 939 var nml = new string[d_mainline.length - 1]; 940 nml.length = 0; 941 942 for (var i = 0; i < d_mainline.length; i++) 943 { 944 if (i != pos) 945 { 946 nml += d_mainline[i]; 947 } 948 } 949 950 d_mainline = nml; 951 } 952 953 store_changed_mainline(); 954 update_walker(); 955 }); 956 957 item.show(); 958 menu.append(item); 959 960 // To keep actions alive as long as the menu is alive 961 menu.set_data("gitg-ext-actions", actions); 962 return menu; 963 } 964 965 private Gtk.Menu? on_refs_list_populate_menu(Gdk.EventButton? event) 966 { 967 if (event != null) 968 { 969 var row = d_main.refs_list.get_row_at_y((int)event.y); 970 d_main.refs_list.select_row(row); 971 } 972 973 var references = d_main.refs_list.selection; 974 975 if (references.is_empty || references.first() != references.last()) 976 { 977 return null; 978 } 979 980 return popup_menu_for_ref(references.first()); 981 } 982 983 private Ggit.OId? id_for_ref(Ggit.Ref r) 984 { 985 Ggit.OId? id = null; 986 987 try 988 { 989 var resolved = r.resolve(); 990 991 if (resolved.is_tag()) 992 { 993 var t = application.repository.lookup<Ggit.Tag>(resolved.get_target()); 994 995 id = t.get_target_id(); 996 } 997 else 998 { 999 id = resolved.get_target(); 1000 } 1001 } 1002 catch {} 1003 1004 return id; 1005 } 1006 1007 private void update_walker() 1008 { 1009 d_selected.clear(); 1010 1011 var include = new Gee.HashSet<Ggit.OId>((Gee.HashDataFunc)Ggit.OId.hash, 1012 (Gee.EqualDataFunc)Ggit.OId.equal); 1013 1014 var isall = d_main.refs_list.is_all; 1015 var isheader = d_main.refs_list.is_header; 1016 1017 var perm_uniq = new Gee.HashSet<Ggit.OId>((Gee.HashDataFunc)Ggit.OId.hash, 1018 (Gee.EqualDataFunc)Ggit.OId.equal); 1019 1020 var permanent = new Ggit.OId[0]; 1021 1022 if (application.repository != null) 1023 { 1024 foreach (var ml in d_mainline) 1025 { 1026 Ggit.OId id; 1027 1028 try 1029 { 1030 id = id_for_ref(application.repository.lookup_reference(ml)); 1031 } catch { continue; } 1032 1033 if (id != null && perm_uniq.add(id)) 1034 { 1035 permanent += id; 1036 } 1037 } 1038 1039 if (d_settings.get_boolean("mainline-head")) 1040 { 1041 try 1042 { 1043 var head = id_for_ref(application.repository.get_head()); 1044 1045 if (head != null && perm_uniq.add(head)) 1046 { 1047 permanent += head; 1048 } 1049 } catch {} 1050 } 1051 } 1052 1053 var show_upstream_with_branch = d_settings.get_boolean("show-upstream-with-branch"); 1054 1055 foreach (var r in d_main.refs_list.selection) 1056 { 1057 var id = id_for_ref(r); 1058 1059 if (id != null) 1060 { 1061 include.add(id); 1062 1063 if (!isall) 1064 { 1065 d_selected.add(id); 1066 1067 if (!isheader && perm_uniq.add(id)) 1068 { 1069 permanent += id; 1070 } 1071 } 1072 1073 if (show_upstream_with_branch && r.is_branch()) 1074 { 1075 var branch = r as Gitg.Branch; 1076 1077 try 1078 { 1079 var upid = id_for_ref(branch.get_upstream()); 1080 1081 if (upid != null) 1082 { 1083 include.add(upid); 1084 } 1085 } catch {} 1086 } 1087 } 1088 } 1089 1090 d_commit_list_model.set_permanent_lanes(permanent); 1091 d_commit_list_model.set_include(include.to_array()); 1092 d_commit_list_model.reload(); 1093 } 1094 1095 public bool search_available 1096 { 1097 get { return true; } 1098 } 1099 1100 private void add_commit_action(Gee.LinkedList<GitgExt.CommitAction> actions, 1101 GitgExt.CommitAction? action) 1102 { 1103 if (action != null && action.available) 1104 { 1105 actions.add(action); 1106 } 1107 } 1108 1109 private string normalize(string s) 1110 { 1111 return s.normalize(-1, NormalizeMode.ALL).casefold(); 1112 } 1113 1114 private bool search_filter_func(Gtk.TreeModel model, int column, string key, Gtk.TreeIter iter) 1115 { 1116 var c = d_commit_list_model.commit_from_iter(iter); 1117 1118 if (c.get_id().has_prefix(key)) 1119 { 1120 return false; 1121 } 1122 1123 var nkey = normalize(key); 1124 var subject = normalize(c.get_subject()); 1125 1126 if (subject.contains(nkey)) 1127 { 1128 return false; 1129 } 1130 1131 var message = normalize(c.get_message()); 1132 1133 if (message.contains(nkey)) 1134 { 1135 return false; 1136 } 1137 1138 return true; 1139 } 1140 1141 public Gtk.Entry? search_entry 1142 { 1143 set 1144 { 1145 d_main.commit_list_view.set_search_entry(value); 1146 1147 if (value != null) 1148 { 1149 d_main.commit_list_view.set_search_column(0); 1150 } 1151 else 1152 { 1153 d_main.commit_list_view.set_search_column(-1); 1154 } 1155 } 1156 } 1157 1158 public string search_text { owned get; set; default = ""; } 1159 public bool search_visible { get; set; } 1160 } 1161} 1162 1163// ex: ts=4 noet 1164