ide-build-panel.c 19.4 KB
Newer Older
1
/* ide-build-panel.c
2
 *
3
 * Copyright © 2015 Christian Hergert <chergert@redhat.com>
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

19 20
#define G_LOG_DOMAIN "ide-build-panel"

21
#include <glib/gi18n.h>
22
#include <ide.h>
23

24 25 26
#include "buildui/ide-build-panel.h"
#include "util/ide-fancy-tree-view.h"
#include "util/ide-cell-renderer-fancy.h"
27

28
struct _IdeBuildPanel
29
{
30
  DzlDockWidget        parent_instance;
31

32
  /* Owned references */
33
  GHashTable          *diags_hash;
34
  IdeBuildPipeline    *pipeline;
35

36 37 38 39 40 41 42 43
  /* Template widgets */
  GtkLabel            *build_status_label;
  GtkLabel            *time_completed_label;
  GtkNotebook         *notebook;
  GtkScrolledWindow   *errors_page;
  IdeFancyTreeView    *errors_tree_view;
  GtkScrolledWindow   *warnings_page;
  IdeFancyTreeView    *warnings_tree_view;
44 45 46 47
  GtkListStore        *diagnostics_store;

  guint                error_count;
  guint                warning_count;
48 49
};

50
G_DEFINE_TYPE (IdeBuildPanel, ide_build_panel, DZL_TYPE_DOCK_WIDGET)
51

52 53 54 55 56
enum {
  COLUMN_DIAGNOSTIC,
  LAST_COLUMN
};

57 58 59 60 61 62 63
enum {
  PROP_0,
  PROP_PIPELINE,
  N_PROPS
};

static GParamSpec *properties [N_PROPS];
64

65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
static void
set_warnings_label (IdeBuildPanel *self,
                    const gchar   *label)
{
  gtk_container_child_set (GTK_CONTAINER (self->notebook), GTK_WIDGET (self->warnings_page),
                           "tab-label", label,
                           NULL);
}

static void
set_errors_label (IdeBuildPanel *self,
                  const gchar   *label)
{
  gtk_container_child_set (GTK_CONTAINER (self->notebook), GTK_WIDGET (self->errors_page),
                           "tab-label", label,
                           NULL);
}

83
static void
84
ide_build_panel_diagnostic (IdeBuildPanel    *self,
85 86
                            IdeDiagnostic    *diagnostic,
                            IdeBuildPipeline *pipeline)
87
{
88
  IdeDiagnosticSeverity severity;
89 90 91
  guint hash;

  IDE_ENTRY;
92

93
  g_assert (IDE_IS_BUILD_PANEL (self));
94
  g_assert (diagnostic != NULL);
95
  g_assert (IDE_IS_BUILD_PIPELINE (pipeline));
96

97 98 99 100
  severity = ide_diagnostic_get_severity (diagnostic);

  if (severity == IDE_DIAGNOSTIC_WARNING)
    {
101
      g_autofree gchar *label = NULL;
102

103
      self->warning_count++;
104

105 106
      label = g_strdup_printf ("%s (%u)", _("Warnings"), self->warning_count);
      set_warnings_label (self, label);
107
    }
108
  else if (severity == IDE_DIAGNOSTIC_ERROR || severity == IDE_DIAGNOSTIC_FATAL)
109
    {
110
      g_autofree gchar *label = NULL;
111

112
      self->error_count++;
113

114 115 116 117 118 119
      label = g_strdup_printf ("%s (%u)", _("Errors"), self->error_count);
      set_errors_label (self, label);
    }
  else
    {
      /* TODO: Figure out design for "Others" Column like Notes? */
120 121
    }

122 123 124 125 126 127
  hash = ide_diagnostic_hash (diagnostic);

  if (g_hash_table_insert (self->diags_hash, GUINT_TO_POINTER (hash), NULL))
    {
      GtkTreeIter iter;

128 129 130 131 132 133
      dzl_gtk_list_store_insert_sorted (self->diagnostics_store,
                                        &iter,
                                        diagnostic,
                                        COLUMN_DIAGNOSTIC,
                                        (GCompareDataFunc)ide_diagnostic_compare,
                                        NULL);
134
      gtk_list_store_set (self->diagnostics_store, &iter,
135
                          COLUMN_DIAGNOSTIC, diagnostic,
136 137 138 139
                          -1);
    }

  IDE_EXIT;
140 141
}

142
static void
143
ide_build_panel_update_running_time (IdeBuildPanel *self)
144
{
145 146
  g_autofree gchar *text = NULL;

147
  g_assert (IDE_IS_BUILD_PANEL (self));
148

149
  if (self->pipeline != NULL)
150
    {
151 152
      IdeBuildManager *build_manager;
      IdeContext *context;
153 154
      GTimeSpan span;

155 156
      context = ide_widget_get_context (GTK_WIDGET (self));
      build_manager = ide_context_get_build_manager (context);
157

158
      span = ide_build_manager_get_running_time (build_manager);
159
      text = dzl_g_time_span_to_label (span);
160
      gtk_label_set_label (self->time_completed_label, text);
161
    }
162 163
  else
    gtk_label_set_label (self->time_completed_label, "—");
164 165 166
}

static void
167
ide_build_panel_started (IdeBuildPanel    *self,
168
                         IdeBuildPhase     phase,
169 170 171 172
                         IdeBuildPipeline *pipeline)
{
  IDE_ENTRY;

173
  g_assert (IDE_IS_BUILD_PANEL (self));
174 175
  g_assert (IDE_IS_BUILD_PIPELINE (pipeline));

176 177 178 179
  if (phase >= IDE_BUILD_PHASE_BUILD)
    {
      self->error_count = 0;
      self->warning_count = 0;
180

181 182
      set_warnings_label (self, _("Warnings"));
      set_errors_label (self, _("Errors"));
183

184 185 186
      gtk_list_store_clear (self->diagnostics_store);
      g_hash_table_remove_all (self->diags_hash);
    }
187 188 189 190 191

  IDE_EXIT;
}

static void
192
ide_build_panel_connect (IdeBuildPanel    *self,
193
                         IdeBuildPipeline *pipeline)
194
{
195
  g_return_if_fail (IDE_IS_BUILD_PANEL (self));
196 197
  g_return_if_fail (IDE_IS_BUILD_PIPELINE (pipeline));
  g_return_if_fail (self->pipeline == NULL);
198

199
  self->pipeline = g_object_ref (pipeline);
200 201 202
  self->error_count = 0;
  self->warning_count = 0;

203 204 205 206 207
  set_warnings_label (self, _("Warnings"));
  set_errors_label (self, _("Errors"));

  gtk_label_set_label (self->time_completed_label, "—");
  gtk_label_set_label (self->build_status_label, "—");
208

209 210
  g_signal_connect_object (pipeline,
                           "diagnostic",
211
                           G_CALLBACK (ide_build_panel_diagnostic),
212 213 214 215 216
                           self,
                           G_CONNECT_SWAPPED);

  g_signal_connect_object (pipeline,
                           "started",
217
                           G_CALLBACK (ide_build_panel_started),
218 219
                           self,
                           G_CONNECT_SWAPPED);
220 221 222
}

static void
223
ide_build_panel_disconnect (IdeBuildPanel *self)
224
{
225
  g_return_if_fail (IDE_IS_BUILD_PANEL (self));
226 227 228
  g_return_if_fail (IDE_IS_BUILD_PIPELINE (self->pipeline));

  g_signal_handlers_disconnect_by_func (self->pipeline,
229
                                        G_CALLBACK (ide_build_panel_diagnostic),
230 231
                                        self);
  g_clear_object (&self->pipeline);
232

233 234
  g_hash_table_remove_all (self->diags_hash);
  gtk_list_store_clear (self->diagnostics_store);
235 236 237
}

void
238
ide_build_panel_set_pipeline (IdeBuildPanel    *self,
239
                              IdeBuildPipeline *pipeline)
240
{
241
  g_return_if_fail (IDE_IS_BUILD_PANEL (self));
242
  g_return_if_fail (!pipeline || IDE_IS_BUILD_PIPELINE (pipeline));
243

244
  if (pipeline != self->pipeline)
245
    {
246
      if (self->pipeline)
247
        ide_build_panel_disconnect (self);
248

249
      if (pipeline)
250
        ide_build_panel_connect (self, pipeline);
251 252 253
    }
}

254
static void
255
ide_build_panel_diagnostic_activated (IdeBuildPanel     *self,
256 257 258
                                      GtkTreePath       *path,
                                      GtkTreeViewColumn *colun,
                                      GtkTreeView       *tree_view)
259 260 261
{
  g_autoptr(IdeUri) uri = NULL;
  IdeSourceLocation *loc;
262
  IdeDiagnostic *diagnostic = NULL;
263
  IdeWorkbench *workbench;
264 265 266 267
  GtkTreeModel *model;
  GtkTreeIter iter;

  IDE_ENTRY;
268

269
  g_assert (IDE_IS_BUILD_PANEL (self));
270 271 272
  g_assert (path != NULL);
  g_assert (GTK_IS_TREE_VIEW_COLUMN (colun));
  g_assert (GTK_IS_TREE_VIEW (tree_view));
273

274 275 276 277
  model = gtk_tree_view_get_model (tree_view);
  if (!gtk_tree_model_get_iter (model, &iter, path))
    IDE_EXIT;

278 279 280 281
  gtk_tree_model_get (model, &iter,
                      COLUMN_DIAGNOSTIC, &diagnostic,
                      -1);

282
  if (diagnostic == NULL)
283
    IDE_EXIT;
284 285 286

  loc = ide_diagnostic_get_location (diagnostic);
  if (loc == NULL)
287
    IDE_EXIT;
288 289 290

  uri = ide_source_location_get_uri (loc);
  if (uri == NULL)
291
    IDE_EXIT;
292 293 294

  workbench = ide_widget_get_workbench (GTK_WIDGET (self));

295 296 297 298 299 300 301
  ide_workbench_open_uri_async (workbench,
                                uri,
                                "editor",
                                IDE_WORKBENCH_OPEN_FLAGS_NONE,
                                NULL,
                                NULL,
                                NULL);
302

303
  IDE_EXIT;
304 305 306
}

static void
307
ide_build_panel_text_func (GtkCellLayout   *layout,
308 309 310 311
                           GtkCellRenderer *renderer,
                           GtkTreeModel    *model,
                           GtkTreeIter     *iter,
                           gpointer         user_data)
312
{
313
  IdeCellRendererFancy *fancy = (IdeCellRendererFancy *)renderer;
314
  g_autoptr(IdeDiagnostic) diagnostic = NULL;
315

316 317 318 319 320
  gtk_tree_model_get (model, iter,
                      COLUMN_DIAGNOSTIC, &diagnostic,
                      -1);

  if G_LIKELY (diagnostic != NULL)
321
    {
322
      g_autofree gchar *title = NULL;
323 324
      g_autofree gchar *name = NULL;
      IdeSourceLocation *location;
325
      const gchar *text;
326 327 328
      GFile *gfile = NULL;
      guint line = 0;
      guint column = 0;
329

330
      location = ide_diagnostic_get_location (diagnostic);
331

332
      if (location != NULL)
333
        {
334 335 336 337 338 339 340 341 342 343 344 345
          IdeFile *file;

          if (NULL != (file = ide_source_location_get_file (location)))
            {
              if (NULL != (gfile = ide_file_get_file (file)))
                {
                  name = g_file_get_basename (gfile);
                  line = ide_source_location_get_line (location);
                  column = ide_source_location_get_line_offset (location);
                }
            }

346
        }
347

348 349
      title = g_strdup_printf ("%s:%u:%u", name ?: "", line + 1, column + 1);
      ide_cell_renderer_fancy_take_title (fancy, g_steal_pointer (&title));
350 351

      text = ide_diagnostic_get_text (diagnostic);
352
      ide_cell_renderer_fancy_set_body (fancy, text);
353
    }
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
  else
    {
      ide_cell_renderer_fancy_set_title (fancy, NULL);
      ide_cell_renderer_fancy_set_body (fancy, NULL);
    }
}

static void
ide_build_panel_notify_message (IdeBuildPanel   *self,
                                GParamSpec      *pspec,
                                IdeBuildManager *build_manager)
{
  g_autofree gchar *message = NULL;
  IdeBuildPipeline *pipeline;
  GtkStyleContext *style;
369

370 371 372 373 374 375 376 377 378 379 380 381 382 383
  g_assert (IDE_IS_BUILD_PANEL (self));
  g_assert (IDE_IS_BUILD_MANAGER (build_manager));

  message = ide_build_manager_get_message (build_manager);
  pipeline = ide_build_manager_get_pipeline (build_manager);

  gtk_label_set_label (self->build_status_label, message);

  style = gtk_widget_get_style_context (GTK_WIDGET (self->build_status_label));

  if (ide_build_pipeline_get_phase (pipeline) == IDE_BUILD_PHASE_FAILED)
    gtk_style_context_add_class (style, GTK_STYLE_CLASS_ERROR);
  else
    gtk_style_context_remove_class (style, GTK_STYLE_CLASS_ERROR);
384 385
}

386
static void
387
ide_build_panel_context_handler (GtkWidget  *widget,
388 389
                                 IdeContext *context)
{
390
  IdeBuildPanel *self = (IdeBuildPanel *)widget;
391 392 393 394
  IdeBuildManager *build_manager;

  IDE_ENTRY;

395
  g_assert (IDE_IS_BUILD_PANEL (self));
396 397 398 399 400 401 402
  g_assert (!context || IDE_IS_CONTEXT (context));

  if (context == NULL)
    IDE_EXIT;

  build_manager = ide_context_get_build_manager (context);

403 404 405 406 407
  g_signal_connect_object (build_manager,
                           "notify::message",
                           G_CALLBACK (ide_build_panel_notify_message),
                           self,
                           G_CONNECT_SWAPPED);
408 409 410

  g_signal_connect_object (build_manager,
                           "notify::running-time",
411
                           G_CALLBACK (ide_build_panel_update_running_time),
412 413 414 415 416
                           self,
                           G_CONNECT_SWAPPED);

  g_signal_connect_object (build_manager,
                           "build-started",
417
                           G_CALLBACK (ide_build_panel_update_running_time),
418 419 420 421 422
                           self,
                           G_CONNECT_SWAPPED);

  g_signal_connect_object (build_manager,
                           "build-finished",
423
                           G_CALLBACK (ide_build_panel_update_running_time),
424 425 426 427 428
                           self,
                           G_CONNECT_SWAPPED);

  g_signal_connect_object (build_manager,
                           "build-failed",
429
                           G_CALLBACK (ide_build_panel_update_running_time),
430 431 432 433 434 435
                           self,
                           G_CONNECT_SWAPPED);

  IDE_EXIT;
}

436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
static gboolean
ide_build_panel_diagnostic_tooltip (IdeBuildPanel *self,
                                    gint           x,
                                    gint           y,
                                    gboolean       keyboard_mode,
                                    GtkTooltip    *tooltip,
                                    GtkTreeView   *tree_view)
{
  GtkTreeModel *model = NULL;
  GtkTreeIter iter;

  g_assert (IDE_IS_BUILD_PANEL (self));
  g_assert (GTK_IS_TOOLTIP (tooltip));
  g_assert (GTK_IS_TREE_VIEW (tree_view));

  if (gtk_tree_view_get_tooltip_context (tree_view, &x, &y, keyboard_mode, &model, NULL, &iter))
    {
      g_autoptr(IdeDiagnostic) diag = NULL;

      gtk_tree_model_get (model, &iter,
                          COLUMN_DIAGNOSTIC, &diag,
                          -1);

      if (diag != NULL)
        {
          g_autofree gchar *text = ide_diagnostic_get_text_for_display (diag);

          gtk_tooltip_set_text (tooltip, text);

          return TRUE;
        }
    }

  return FALSE;
}

static gboolean
diagnostic_is_warning (GtkTreeModel *model,
                       GtkTreeIter  *iter,
                       gpointer      user_data)
{
  g_autoptr(IdeDiagnostic) diag = NULL;
  IdeDiagnosticSeverity severity = 0;

  gtk_tree_model_get (model, iter,
                      COLUMN_DIAGNOSTIC, &diag,
                      -1);

  if (diag != NULL)
    severity = ide_diagnostic_get_severity (diag);

  return severity <= IDE_DIAGNOSTIC_WARNING;
}

static gboolean
diagnostic_is_error (GtkTreeModel *model,
                     GtkTreeIter  *iter,
                     gpointer      user_data)
{
  g_autoptr(IdeDiagnostic) diag = NULL;
  IdeDiagnosticSeverity severity = 0;

  gtk_tree_model_get (model, iter,
                      COLUMN_DIAGNOSTIC, &diag,
                      -1);

  if (diag != NULL)
    severity = ide_diagnostic_get_severity (diag);

  return severity > IDE_DIAGNOSTIC_WARNING;
}

508
static void
509
ide_build_panel_destroy (GtkWidget *widget)
510
{
511
  IdeBuildPanel *self = (IdeBuildPanel *)widget;
512

513
  if (self->pipeline != NULL)
514
    ide_build_panel_disconnect (self);
515

516
  g_clear_pointer (&self->diags_hash, g_hash_table_unref);
517

518
  GTK_WIDGET_CLASS (ide_build_panel_parent_class)->destroy (widget);
519 520 521
}

static void
522
ide_build_panel_get_property (GObject    *object,
523 524 525 526
                              guint       prop_id,
                              GValue     *value,
                              GParamSpec *pspec)
{
527
  IdeBuildPanel *self = IDE_BUILD_PANEL (object);
528 529 530

  switch (prop_id)
    {
531 532
    case PROP_PIPELINE:
      g_value_set_object (value, self->pipeline);
533 534 535
      break;

    default:
536
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
537 538 539 540
    }
}

static void
541
ide_build_panel_set_property (GObject      *object,
542 543 544 545
                              guint         prop_id,
                              const GValue *value,
                              GParamSpec   *pspec)
{
546
  IdeBuildPanel *self = IDE_BUILD_PANEL (object);
547 548 549

  switch (prop_id)
    {
550
    case PROP_PIPELINE:
551
      ide_build_panel_set_pipeline (self, g_value_get_object (value));
552 553 554
      break;

    default:
555
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
556 557 558 559
    }
}

static void
560
ide_build_panel_class_init (IdeBuildPanelClass *klass)
561 562 563 564
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

565
  widget_class->destroy = ide_build_panel_destroy;
566

567 568
  object_class->get_property = ide_build_panel_get_property;
  object_class->set_property = ide_build_panel_set_property;
569

570 571 572 573 574 575
  properties [PROP_PIPELINE] =
    g_param_spec_object ("pipeline",
                         NULL,
                         NULL,
                         IDE_TYPE_BUILD_PIPELINE,
                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
576

577
  g_object_class_install_properties (object_class, N_PROPS, properties);
578

579
  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/plugins/buildui/ide-build-panel.ui");
580
  gtk_widget_class_set_css_name (widget_class, "buildpanel");
581 582 583 584 585 586 587
  gtk_widget_class_bind_template_child (widget_class, IdeBuildPanel, build_status_label);
  gtk_widget_class_bind_template_child (widget_class, IdeBuildPanel, time_completed_label);
  gtk_widget_class_bind_template_child (widget_class, IdeBuildPanel, notebook);
  gtk_widget_class_bind_template_child (widget_class, IdeBuildPanel, errors_page);
  gtk_widget_class_bind_template_child (widget_class, IdeBuildPanel, errors_tree_view);
  gtk_widget_class_bind_template_child (widget_class, IdeBuildPanel, warnings_page);
  gtk_widget_class_bind_template_child (widget_class, IdeBuildPanel, warnings_tree_view);
588
  gtk_widget_class_bind_template_child (widget_class, IdeBuildPanel, diagnostics_store);
589

590
  g_type_ensure (IDE_TYPE_CELL_RENDERER_FANCY);
591
  g_type_ensure (IDE_TYPE_DIAGNOSTIC);
592
  g_type_ensure (IDE_TYPE_FANCY_TREE_VIEW);
593 594 595
}

static void
596
ide_build_panel_init (IdeBuildPanel *self)
597
{
598 599
  GtkTreeModel *filter;

600 601
  gtk_widget_init_template (GTK_WIDGET (self));

602 603
  self->diags_hash = g_hash_table_new (NULL, NULL);

604
  g_object_set (self, "title", _("Build Issues"), NULL);
605

606
  ide_widget_set_context_handler (self, ide_build_panel_context_handler);
607

608
  g_signal_connect_swapped (self->warnings_tree_view,
609
                           "row-activated",
610
                           G_CALLBACK (ide_build_panel_diagnostic_activated),
611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
                           self);

  g_signal_connect_swapped (self->warnings_tree_view,
                           "query-tooltip",
                           G_CALLBACK (ide_build_panel_diagnostic_tooltip),
                           self);

  g_signal_connect_swapped (self->errors_tree_view,
                           "row-activated",
                           G_CALLBACK (ide_build_panel_diagnostic_activated),
                           self);

  g_signal_connect_swapped (self->errors_tree_view,
                           "query-tooltip",
                           G_CALLBACK (ide_build_panel_diagnostic_tooltip),
                           self);

  filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (self->diagnostics_store), NULL);
  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
                                          diagnostic_is_warning, NULL, NULL);
  gtk_tree_view_set_model (GTK_TREE_VIEW (self->warnings_tree_view), GTK_TREE_MODEL (filter));
  g_object_unref (filter);

  filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (self->diagnostics_store), NULL);
  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
                                          diagnostic_is_error, NULL, NULL);
  gtk_tree_view_set_model (GTK_TREE_VIEW (self->errors_tree_view), GTK_TREE_MODEL (filter));
  g_object_unref (filter);

  ide_fancy_tree_view_set_data_func (IDE_FANCY_TREE_VIEW (self->warnings_tree_view),
                                     ide_build_panel_text_func, self, NULL);
642

643
  ide_fancy_tree_view_set_data_func (IDE_FANCY_TREE_VIEW (self->errors_tree_view),
644
                                     ide_build_panel_text_func, self, NULL);
645
}