box_data_calendar_related.cc 18.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* Glom
 *
 * Copyright (C) 2001-2004 Murray Cumming
 *
 * 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 2 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, write to the
17 18
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
19 20
 */

21
#include <glom/mode_data/box_data_calendar_related.h>
22
#include <glom/mode_design/layout/dialog_layout_calendar_related.h>
23
#include <glom/utils_ui.h>
24
#include <glom/appwindow.h>
25
#include <libglom/data_structure/glomconversions.h>
26
#include <libglom/sql_utils.h>
27
#include <libglom/db_utils.h>
28
#include <libglom/utils.h>
29
#include <glom/glade_utils.h>
30
#include <giomm/menu.h>
31 32 33 34 35
#include <glibmm/i18n.h>

namespace Glom
{

36
Box_Data_Calendar_Related::Box_Data_Calendar_Related()
37
: m_query_column_date_field(-1)
38 39 40
{
  set_size_request(400, -1); //An arbitrary default.

41
  m_Frame.add(m_calendar);
42 43
  m_calendar.set_margin_start(Utils::to_utype(UiUtils::DefaultSpacings::LARGE));
  m_calendar.set_margin_top(Utils::to_utype(UiUtils::DefaultSpacings::SMALL));   
44
  m_calendar.show();
45

46 47
  //m_calendar.set_show_details();
  m_calendar.set_detail_width_chars(7);
48 49
  m_calendar.set_detail_height_rows(2);

50 51
  //Tell the calendar how to get the record details to show:
  m_calendar.set_detail_func( sigc::mem_fun(*this, &Box_Data_Calendar_Related::on_calendar_details) );
52

53
  m_calendar.signal_month_changed().connect( sigc::mem_fun(*this, &Box_Data_Calendar_Related::on_calendar_month_changed) );
54

55
  setup_menu(this);
56 57
  //m_calendar.add_events(Gdk::BUTTON_PRESS_MASK); //Allow us to catch button_press_event and button_release_event
  m_calendar.signal_button_press_event().connect_notify( sigc::mem_fun(*this, &Box_Data_Calendar_Related::on_calendar_button_press_event) );
58

59 60 61
  //We do not actually use this,
  //so it is a bug if this appears in the .glom file:
  m_layout_name = "NotUsedlist_related_calendar";
62 63
}

64 65 66 67 68
Box_Data_Calendar_Related::~Box_Data_Calendar_Related()
{
  clear_cached_database_values();
}

69
void Box_Data_Calendar_Related::enable_buttons()
70
{
71 72
  //const bool view_details_possible = get_has_suitable_record_to_view_details();
  //m_calendar.set_allow_view_details(view_details_possible); //Don't allow the user to go to a record in a hidden table.
73 74
}

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
void Box_Data_Calendar_Related::show_title_in_ui(const Glib::ustring& title)
{
  if (!title.empty()) {
    m_Label.set_markup(UiUtils::bold_message(title));
    m_Label.show();

    m_calendar.set_margin_start(Utils::to_utype(UiUtils::DefaultSpacings::LARGE));
    m_calendar.set_margin_top(Utils::to_utype(UiUtils::DefaultSpacings::SMALL));
  }
  else {
    m_Label.set_markup(Glib::ustring());
    m_Label.hide();

    //The box itself has padding of 6.
    m_calendar.set_margin_start(0);
    m_calendar.set_margin_top(0);
  }
}

94 95 96 97 98 99 100
void Box_Data_Calendar_Related::create_layout()
{
  Box_Data::create_layout();

  m_FieldsShown = get_fields_to_show();
}

101
bool Box_Data_Calendar_Related::fill_from_database()
102
{
103 104
  const auto portal = get_portal();
  if(!portal)
105
    return false;
106

107 108 109 110 111 112
  bool result = false;

  if(m_key_field && m_found_set.m_where_clause.empty()) //There's a key field, but no value.
  {
    //No Foreign Key value, so just show the field names:

113
    result = Base_DB_Table_Data::fill_from_database();
114 115 116 117 118

    //create_layout();
  }
  else
  {
119 120
    if(m_query_column_date_field == -1)
      return false; //This is useless without the date in the result.
121

122 123 124 125 126 127
    //Create a date range from the beginning to end of the selected month:
    Glib::Date calendar_date;
    m_calendar.get_date(calendar_date);
    const Glib::Date date_start(1, calendar_date.get_month(), calendar_date.get_year());
    Glib::Date date_end = date_start;
    date_end.add_months(1);
128

129 130
    Gnome::Gda::Value date_start_value(date_start);
    Gnome::Gda::Value date_end_value(date_end);
131

132
    //Add a WHERE clause for this date range:
133
    auto relationship = portal->get_relationship();
134
    Glib::ustring where_clause_to_table_name = relationship->get_to_table();
135

136
    auto derived_portal = std::dynamic_pointer_cast<LayoutItem_CalendarPortal>(portal);
Murray Cumming's avatar
Murray Cumming committed
137
    const auto date_field_name = derived_portal->get_date_field()->get_name();
138

139
    auto relationship_related = portal->get_related_relationship();
140
    if(relationship_related)
141
    {
142
      //Adjust the WHERE clause appropriately for the extra JOIN:
143
      auto uses_rel_temp = std::make_shared<UsesRelationship>();
144 145
      uses_rel_temp->set_relationship(relationship);
      where_clause_to_table_name = uses_rel_temp->get_sql_join_alias_name();
146 147
    }

148
    //Add an AND to the existing where clause, to get only records within these dates, if any:
149
    auto date_field = derived_portal->get_date_field();
150

151
    auto builder =
152
      Gnome::Gda::SqlBuilder::create(Gnome::Gda::SQL_STATEMENT_SELECT);
Murray Cumming's avatar
Murray Cumming committed
153
    const auto cond = builder->add_cond(Gnome::Gda::SQL_OPERATOR_TYPE_BETWEEN,
154
       builder->add_field_id(date_field->get_name(), m_found_set.m_table_name),
155 156 157
       builder->add_expr_as_value(date_start_value),
       builder->add_expr_as_value(date_end_value));
    builder->set_where(cond); //Might be unnecessary.
Murray Cumming's avatar
Murray Cumming committed
158
    const auto extra_where_clause = builder->export_expression(cond);
159 160

    Gnome::Gda::SqlExpr where_clause;
161
    if(m_found_set.m_where_clause.empty())
162
    {
163
      where_clause = extra_where_clause;
164
    }
165
    else
166
    {
167
      where_clause = SqlUtils::build_combined_where_expression(
168 169 170 171
        m_found_set.m_where_clause, extra_where_clause,
        Gnome::Gda::SQL_OPERATOR_TYPE_AND);
    }

172 173
    //Do one SQL query for the whole month and store the cached values here:
    clear_cached_database_values();
174

175
    auto sql_query = SqlUtils::build_sql_select_with_where_clause(m_found_set.m_table_name, m_FieldsShown, where_clause, m_found_set.m_extra_join, m_found_set.m_sort_clause);
176
    //std::cout << "DEBUG: sql_query=" << sql_query << std::endl;
177
    auto datamodel = DbUtils::query_execute_select(sql_query);
178 179
    if(!(datamodel))
      return true;
180

Murray Cumming's avatar
Murray Cumming committed
181
    const auto rows_count = datamodel->get_n_rows();
182 183
    if(!(rows_count > 0))
      return true;
184

185 186 187
    //Get the data:
    for(int row_index = 0; row_index < rows_count; ++row_index)
    {
Murray Cumming's avatar
Murray Cumming committed
188
      const auto columns_count = datamodel->get_n_columns();
189 190
      if(m_query_column_date_field > columns_count)
       continue;
191

192
      //Get the date value for this row:
Murray Cumming's avatar
Murray Cumming committed
193 194
      const auto value_date = datamodel->get_value_at(m_query_column_date_field, row_index);
      const auto date = value_date.get_date();
195

196
      //Get all the values for this row:
Murray Cumming's avatar
Murray Cumming committed
197
      auto pVector = new type_vector_values(m_FieldsShown.size());
198 199 200 201
      for(int column_index = 0; column_index < columns_count; ++column_index)
      {
        (*pVector)[column_index] = datamodel->get_value_at(column_index, row_index);
      }
202

203
      m_map_values[date].emplace_back(pVector);
204
    }
205 206
  }

207

208 209 210
  return result;
}

211 212
void Box_Data_Calendar_Related::clear_cached_database_values()
{
Murray Cumming's avatar
Murray Cumming committed
213
  for(const auto& the_pair : m_map_values)
214
  {
Murray Cumming's avatar
Murray Cumming committed
215
    const auto& vec = the_pair.second;
Murray Cumming's avatar
Murray Cumming committed
216
    for(const auto& pValues : vec)
217
    {
218
      delete pValues;
219 220
    }
  }
221 222

  m_map_values.clear();
223
}
224 225

//TODO: Make this generic in Box_Data_Portal:
226
void Box_Data_Calendar_Related::on_record_added(const Gnome::Gda::Value& primary_key_value, const Gtk::TreeModel::iterator& row)
227 228 229 230 231 232 233 234 235 236 237 238
{
  //primary_key_value is a new autogenerated or human-entered key for the row.
  //It has already been added to the database.

  if(!row)
    return;

  Gnome::Gda::Value key_value;

  if(m_key_field)
  {
    //m_key_field is the field in this table that must match another field in the parent table.
239
    auto layout_item = std::make_shared<LayoutItem_Field>();
240
    layout_item->set_full_field_details(m_key_field);
241
    //TODO: key_value = m_calendar.get_value(row, layout_item);
242 243 244 245 246 247 248 249 250 251 252 253
  }

  //Make sure that the new related record is related,
  //by setting the foreign key:
  //If it's not auto-generated.
  if(!Conversions::value_is_empty(key_value)) //If there is already a value.
  {
    //It was auto-generated. Tell the parent about it, so it can make a link.
    signal_record_added.emit(key_value);
  }
  else if(Conversions::value_is_empty(m_key_value))
  {
254
    std::cerr << G_STRFUNC << ": m_key_value is NULL.\n";
255 256 257
  }
  else
  {
258
    std::shared_ptr<Field> field_primary_key; //TODO: = m_calendar.get_key_field();
259

260 261
    const auto portal = get_portal();

262
    //Create the link by setting the foreign key
263
    if(m_key_field && portal)
264
    {
265
      auto builder = Gnome::Gda::SqlBuilder::create(Gnome::Gda::SQL_STATEMENT_UPDATE);
266
      const auto target_table = portal->get_table_used(Glib::ustring() /* not relevant */);
267
      builder->set_table(target_table);
Murray Cumming's avatar
Murray Cumming committed
268 269 270
      builder->add_field_value_as_value(m_key_field->get_name(), m_key_value);
      builder->set_where(
        builder->add_cond(Gnome::Gda::SQL_OPERATOR_TYPE_EQ,
271
          builder->add_field_id(field_primary_key->get_name(), target_table),
Murray Cumming's avatar
Murray Cumming committed
272
          builder->add_expr_as_value(primary_key_value)));
273

Murray Cumming's avatar
Murray Cumming committed
274
      const auto test = DbUtils::query_execute(builder);
275 276 277
      if(test)
      {
        //Show it on the view, if it's visible:
278
        auto layout_item = std::make_shared<LayoutItem_Field>();
279 280
        layout_item->set_full_field_details(field_primary_key);

281
        //TODO: m_calendar.set_value(row, layout_item, m_key_value);
282 283 284 285 286 287 288
      }
    }

    //on_adddel_user_changed(row, iKey); //Update the database.
  }
}

289
Box_Data_Calendar_Related::type_vecConstLayoutFields Box_Data_Calendar_Related::get_fields_to_show() const
290
{
Murray Cumming's avatar
Murray Cumming committed
291
  auto layout_fields = Box_Data_Portal::get_fields_to_show();
292

293 294
  const auto portal = get_portal();
  const auto derived_portal = std::dynamic_pointer_cast<const LayoutItem_CalendarPortal>(portal);
295
  if(!derived_portal)
296
  {
297
    std::cerr << G_STRFUNC << ": The portal is not a LayoutItem_CalendarPortal.\n";
298
    return layout_fields;
299
  }
300

301
  auto date_field = derived_portal->get_date_field();
302
  if(!date_field)
303
  {
304
    std::cerr << G_STRFUNC << ": get_date_field() returned no field.\n";
305
    return layout_fields;
306
  }
307

308
  //Add it to the list to ensure that we request the date (though it will not really be shown in the calendar):
309
  auto layout_item_date_field = std::make_shared<LayoutItem_Field>();
310
  layout_item_date_field->set_full_field_details(date_field);
311
  layout_fields.emplace_back(layout_item_date_field);
312 313
  m_query_column_date_field = layout_fields.size() - 1;
  return layout_fields;
314 315 316
}

#ifndef GLOM_ENABLE_CLIENT_ONLY
317
void Box_Data_Calendar_Related::on_dialog_layout_hide()
318
{
319
  auto dialog_related = dynamic_cast<Dialog_Layout_Calendar_Related*>(m_dialog_layout);
320
  g_assert(dialog_related);
321 322
  const auto portal = dialog_related->get_portal_layout();
  set_layout_item(portal, "" /* TODO */);
323 324

  //Update the UI:
325
  auto derived_portal = std::dynamic_pointer_cast<LayoutItem_CalendarPortal>(portal);
326
  init_db_details(derived_portal);
327 328 329

  Box_Data::on_dialog_layout_hide();

330
  auto pLayoutItem = std::dynamic_pointer_cast<LayoutItem_CalendarPortal>(get_layout_item());
331 332
  if(pLayoutItem)
  {
333 334
    if(derived_portal)
      *pLayoutItem = *derived_portal;
335

336 337 338 339 340
    signal_layout_changed().emit(); //TODO: Check whether it has really changed.
  }
}
#endif // !GLOM_ENABLE_CLIENT_ONLY

341
#ifndef GLOM_ENABLE_CLIENT_ONLY
342
Dialog_Layout* Box_Data_Calendar_Related::create_layout_dialog() const
343
{
Murray Cumming's avatar
Murray Cumming committed
344
  Dialog_Layout_Calendar_Related* dialog = nullptr;
345 346
  Glom::Utils::get_glade_widget_derived_with_warning(dialog);
  return dialog;
347 348 349 350
}

void Box_Data_Calendar_Related::prepare_layout_dialog(Dialog_Layout* dialog)
{
Murray Cumming's avatar
Murray Cumming committed
351
  auto related_dialog = dynamic_cast<Dialog_Layout_Calendar_Related*>(dialog);
352
  g_assert(related_dialog);
353

354 355
  const auto portal = get_portal();
  const auto derived_portal = std::dynamic_pointer_cast<LayoutItem_CalendarPortal>(portal);
356 357
  if(derived_portal && derived_portal->get_has_relationship_name())
  {
358
    related_dialog->init_with_portal(m_layout_name, m_layout_platform, get_document(), derived_portal);
359 360 361
  }
  else
  {
362
    related_dialog->init_with_tablename(m_layout_name, m_layout_platform, get_document(), get_parent_table());
363
  }
364
}
365
#endif // !GLOM_ENABLE_CLIENT_ONLY
366

367 368 369 370 371 372
void Box_Data_Calendar_Related::on_calendar_month_changed()
{
  //Update the cached values for the new month:
  fill_from_database();
}

373 374
Glib::ustring Box_Data_Calendar_Related::on_calendar_details(guint year, guint month, guint day)
{
375 376
  const auto portal = get_portal();
  const auto derived_portal = std::dynamic_pointer_cast<LayoutItem_CalendarPortal>(portal);
377
  if(!derived_portal)
378
  {
379
    //std::cout << "debug: " << G_STRFUNC << ": date_field is NULL\n";
380 381
    return Glib::ustring();
  }
382

383
  auto date_field = derived_portal->get_date_field();
384
  if(!date_field)
385
  {
386
    std::cerr << G_STRFUNC << ":  get_date_field() returned no field.\n";
387
    return Glib::ustring();
388
  }
389

390
  //TODO: month seems to be 143710360 sometimes, which seems to be a GtkCalendar bug:
391
  //std::cout << "debug: " << G_STRFUNC << ": year=" << year << ", month=" << month << " day=" << day << std::endl;
392 393 394 395 396 397

  //Glib::Date is 1-indexed:
  Glib::Date::Month datemonth = (Glib::Date::Month)(month +1);
  if(datemonth > Glib::Date::DECEMBER)
    datemonth = Glib::Date::JANUARY;
  Glib::Date date(day, datemonth, year);
398

399
  //Examine the cached data:
400
  const auto iter_find = m_map_values.find(date);
401 402
  if(iter_find == m_map_values.end())
    return Glib::ustring(); //No data was found for this date.
403 404


405
  Glib::ustring result;
406

407
  //Look at each row for this date:
Murray Cumming's avatar
Murray Cumming committed
408
  const auto& rows = iter_find->second;
409
  for(const auto& pRow : rows)
410
  {
411 412
    if(!pRow)
      continue;
413

414 415 416
    //Get the data for each column in the row:
    Glib::ustring row_text;
    int column_index = 0;
417

418 419 420
    //We iterate over the original list of items from the portal,
    //instead of the ones used by the query (m_FieldsShown),
    //because we really don't want to show the extra fields (at the end) to the user:
421
    LayoutGroup::type_list_items items = portal->get_items();
422
    for(const auto& layout_item : items)
423
    {
424 425
      if(!layout_item)
        continue;
426

427
      Glib::ustring text;
428

429
      //Text for a text item:
430
      auto layout_item_text = std::dynamic_pointer_cast<const LayoutItem_Text>(layout_item);
431
      if(layout_item_text)
432
        text = layout_item_text->get_text(AppWindow::get_current_locale());
433
      else
434
      {
435
        //Text for a field:
436
        auto layout_item_field = std::dynamic_pointer_cast<const LayoutItem_Field>(layout_item);
437

438
        const Gnome::Gda::Value value = (*pRow)[column_index];
439
        text = Conversions::get_text_for_gda_value(layout_item_field->get_glom_type(), value, layout_item_field->get_formatting_used().m_numeric_format);
440

441 442
        ++column_index;
      }
443

444 445 446 447 448
      //Add the field text to the row:
      if(!text.empty())
      {
        if(!row_text.empty())
          row_text += ", "; //TODO: Internationalization?
449

450 451
        row_text += text;
      }
452
    }
453

454 455
    //Add the row text to the result:
    if(!row_text.empty())
456 457
    {
      if(!result.empty())
458
        result += '\n';
459

460
      result += row_text;
461 462
    }
  }
463

464 465 466
  return result;
}

467
void Box_Data_Calendar_Related::setup_menu(Gtk::Widget* /* this */)
468
{
469
  m_action_group = Gio::SimpleActionGroup::create();
470

471
  m_context_edit = m_action_group->add_action("edit",
472 473 474 475 476
    sigc::mem_fun(*this, &Box_Data_Calendar_Related::on_MenuPopup_activate_Edit) );

#ifndef GLOM_ENABLE_CLIENT_ONLY
  // Don't add ContextLayout in client only mode because it would never
  // be sensitive anyway
477
  m_context_layout =  m_action_group->add_action("layout",
478 479 480
    sigc::mem_fun(*this, &Box_Data_Calendar_Related::on_MenuPopup_activate_layout) );

  //TODO: This does not work until this widget is in a container in the window:
Murray Cumming's avatar
Murray Cumming committed
481
  auto pApp = get_appwindow();
482 483
  if(pApp)
  {
484
    pApp->add_developer_action(m_context_layout); //So that it can be disabled when not in developer mode.
485
    pApp->update_userlevel_ui(); //Update our action's sensitivity.
486 487 488
  }
#endif // !GLOM_ENABLE_CLIENT_ONLY

489
  insert_action_group("context", m_action_group);
490 491 492

  //TODO: add_accel_group(m_refUIManager->get_accel_group());

493
  auto menu = Gio::Menu::create();
494 495
  menu->append(_("_Edit"), "context.edit");
  menu->append(_("_Layout"), "context.layout");
496

497 498
  m_menu_popup = std::make_unique<Gtk::Menu>(menu);
  m_menu_popup->attach_to_widget(*this);
499

500 501
#ifndef GLOM_ENABLE_CLIENT_ONLY
  if(pApp)
502
    m_context_layout->set_enabled(pApp->get_userlevel() == AppState::userlevels::DEVELOPER);
503 504 505
#endif // !GLOM_ENABLE_CLIENT_ONLY
}

506
void Box_Data_Calendar_Related::on_calendar_button_press_event(GdkEventButton *button_event)
507
{
508 509
#ifndef GLOM_ENABLE_CLIENT_ONLY
  //Enable/Disable items.
510
  //We did this earlier, but get_appwindow is more likely to work now:
Murray Cumming's avatar
Murray Cumming committed
511
  auto pApp = get_appwindow();
512 513
  if(pApp)
  {
514
    pApp->add_developer_action(m_context_layout); //So that it can be disabled when not in developer mode.
515
    pApp->update_userlevel_ui(); //Update our action's sensitivity.
516 517 518 519
  }
#endif

  GdkModifierType mods;
520
  gdk_window_get_device_position( gtk_widget_get_window(Gtk::Widget::gobj()), button_event->device, 0, 0, &mods );
521 522 523
  if(mods & GDK_BUTTON3_MASK)
  {
    //Give user choices of actions on this item:
524
    m_menu_popup->popup(button_event->button, button_event->time);
525 526 527 528
    return; //handled.
  }
  else
  {
529
    if(button_event->type == GDK_2BUTTON_PRESS)
530 531 532 533 534 535 536 537 538 539 540
    {
      //Double-click means edit.
      //Don't do this usually, because users sometimes double-click by accident when they just want to edit a cell.

      //TODO: If the cell is not editable, handle the double-click as an edit/selection.
      //on_MenuPopup_activate_Edit();
      return; //Not handled.
    }
  }

  return; //Not handled. TODO: Call base class?
541 542
}

543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
void
Box_Data_Calendar_Related::on_MenuPopup_activate_Edit()
{
  const Gnome::Gda::Value primary_key_value; //TODO: = m_AddDel.get_value_key(row); //The primary key is in the key.

  signal_user_requested_details().emit(primary_key_value);
}

#ifndef GLOM_ENABLE_CLIENT_ONLY
void Box_Data_Calendar_Related::on_MenuPopup_activate_layout()
{
  show_layout_dialog();
}
#endif // !GLOM_ENABLE_CLIENT_ONLY

558
Gnome::Gda::Value Box_Data_Calendar_Related::get_primary_key_value(const Gtk::TreeModel::iterator& /* row */) const
559 560 561 562 563 564 565 566 567 568 569 570 571
{
  return Gnome::Gda::Value(); //TODO: m_AddDel.get_value_key(row);
}

Gnome::Gda::Value Box_Data_Calendar_Related::get_primary_key_value_selected() const
{
  return Gnome::Gda::Value(); //TODO: m_AddDel.get_value_key_selected();
}

void Box_Data_Calendar_Related::set_primary_key_value(const Gtk::TreeModel::iterator& /* row */, const Gnome::Gda::Value& /* value */)
{
  //TODO
}
572

573
} //namespace Glom