MythTV  master
customedit.cpp
Go to the documentation of this file.
1 // Qt
2 #include <QSqlError>
3 
4 // MythTV
6 #include "libmythbase/mythdb.h"
11 #include "libmythui/mythuitext.h"
13 
14 // MythFrontend
15 #include "customedit.h"
16 #include "proglist.h"
17 #include "scheduleeditor.h"
18 
19 
21  : MythScreenType(parent, "CustomEdit")
22 {
23  if (pginfo)
24  m_pginfo = new ProgramInfo(*pginfo);
25  else
26  m_pginfo = new ProgramInfo();
27 
30 
31  m_seSuffix = QString(" (%1)").arg(tr("stored search"));
32  m_exSuffix = QString(" (%1)").arg(tr("stored example"));
33 
35 }
36 
38 {
39  delete m_pginfo;
40 
42 }
43 
45 {
46  if (!LoadWindowFromXML("schedule-ui.xml", "customedit", this))
47  return false;
48 
49  bool err = false;
50  UIUtilE::Assign(this, m_ruleList, "rules", &err);
51  UIUtilE::Assign(this, m_clauseList, "clauses", &err);
52 
53  UIUtilE::Assign(this, m_titleEdit, "title", &err);
54  UIUtilE::Assign(this, m_subtitleEdit, "subtitle", &err);
55  UIUtilE::Assign(this, m_descriptionEdit, "description", &err);
56  UIUtilE::Assign(this, m_clauseText, "clausetext", &err);
57  UIUtilE::Assign(this, m_testButton, "test", &err);
58  UIUtilE::Assign(this, m_recordButton, "record", &err);
59  UIUtilE::Assign(this, m_storeButton, "store", &err);
60  UIUtilE::Assign(this, m_cancelButton, "cancel", &err);
61 
62  if (err)
63  {
64  LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'customedit'");
65  return false;
66  }
67 
77 
82 
87 
88  loadData();
90 
91  return true;
92 }
93 
94 
96 {
97  CustomRuleInfo rule;
98 
99  // New Rule defaults
100  rule.recordid = '0';
101  rule.title = m_baseTitle;
102  if (!m_baseTitle.isEmpty())
103  {
104  QString quoteTitle = m_baseTitle;
105  quoteTitle.replace("\'","\'\'");
106  rule.description = QString("program.title = '%1' ").arg(quoteTitle);
107  }
108 
109  new MythUIButtonListItem(m_ruleList, tr("<New rule>"),
110  QVariant::fromValue(rule));
111 
112  MSqlQuery result(MSqlQuery::InitCon());
113  result.prepare("SELECT recordid, title, subtitle, description "
114  "FROM record WHERE search = :SEARCH ORDER BY title;");
115  result.bindValue(":SEARCH", kPowerSearch);
116 
117  if (result.exec())
118  {
119  // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
120  while (result.next())
121  {
122  QString trimTitle = result.value(1).toString();
123  trimTitle.remove(RecordingInfo::kReSearchTypeName);
124 
125  rule.recordid = result.value(0).toString();
126  rule.title = trimTitle;
127  rule.subtitle = result.value(2).toString();
128  rule.description = result.value(3).toString();
129 
130  // No memory leak. MythUIButtonListItem adds the new item
131  // into m_ruleList.
132  auto *item = new MythUIButtonListItem(m_ruleList, rule.title,
133  QVariant::fromValue(rule));
134 
135  if (trimTitle == m_baseTitle ||
136  result.value(0).toUInt() == m_pginfo->GetRecordingRuleID())
137  m_ruleList->SetItemCurrent(item);
138  }
139  }
140  else
141  MythDB::DBError("Get power search rules query", result);
142 
143  loadClauses();
144 
145  textChanged();
146 }
147 
148 QString CustomEdit::evaluate(QString clause)
149 {
150  static const QRegularExpression replacement { "\\{([A-Z]+)\\}" };
151 
152  while (true) {
153  QRegularExpressionMatch match;
154  if (!clause.contains(replacement, &match))
155  break;
156 
157  QString mid = match.captured(1);
158  QString repl = "";
159 
160  if (mid.compare("TITLE") == 0) {
161  repl = m_pginfo->GetTitle();
162  repl.replace("\'","\'\'");
163  } else if (mid.compare("SUBTITLE") == 0) {
164  repl = m_pginfo->GetSubtitle();
165  repl.replace("\'","\'\'");
166  } else if (mid.compare("DESCR") == 0) {
167  repl = m_pginfo->GetDescription();
168  repl.replace("\'","\'\'");
169  } else if (mid.compare("SERIESID") == 0) {
170  repl = QString("%1").arg(m_pginfo->GetSeriesID());
171  } else if (mid.compare("PROGID") == 0) {
172  repl = m_pginfo->GetProgramID();
173  } else if (mid.compare("SEASON") == 0) {
174  repl = QString::number(m_pginfo->GetSeason());
175  } else if (mid.compare("EPISODE") == 0) {
176  repl = QString::number(m_pginfo->GetEpisode());
177  } else if (mid.compare("CATEGORY") == 0) {
178  repl = m_pginfo->GetCategory();
179  } else if (mid.compare("CHANID") == 0) {
180  repl = QString("%1").arg(m_pginfo->GetChanID());
181  } else if (mid.compare("CHANNUM") == 0) {
182  repl = m_pginfo->GetChanNum();
183  } else if (mid.compare("SCHEDID") == 0) {
185  } else if (mid.compare("CHANNAME") == 0) {
186  repl = m_pginfo->GetChannelName();
187  } else if (mid.compare("DAYNAME") == 0) {
188  repl = m_pginfo->GetScheduledStartTime().toString("dddd");
189  } else if (mid.compare("STARTDATE") == 0) {
190  repl = m_pginfo->GetScheduledStartTime().toString("yyyy-mm-dd hh:mm:ss");
191  } else if (mid.compare("ENDDATE") == 0) {
192  repl = m_pginfo->GetScheduledEndTime().toString("yyyy-mm-dd hh:mm:ss");
193  } else if (mid.compare("STARTTIME") == 0) {
194  repl = m_pginfo->GetScheduledStartTime().toString("hh:mm");
195  } else if (mid.compare("ENDTIME") == 0) {
196  repl = m_pginfo->GetScheduledEndTime().toString("hh:mm");
197  } else if (mid.compare("STARTSEC") == 0) {
198  QDateTime date = m_pginfo->GetScheduledStartTime();
199 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
200  QDateTime midnight = QDateTime(date.date());
201 #else
202  QDateTime midnight = date.date().startOfDay();
203 #endif
204  repl = QString("%1").arg(midnight.secsTo(date));
205  } else if (mid.compare("ENDSEC") == 0) {
206  QDateTime date = m_pginfo->GetScheduledEndTime();
207 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
208  QDateTime midnight = QDateTime(date.date());
209 #else
210  QDateTime midnight = date.date().startOfDay();
211 #endif
212  repl = QString("%1").arg(midnight.secsTo(date));
213  }
214  // unknown tags are simply removed
215  clause.replace(match.capturedStart(), match.capturedLength(), repl);
216  }
217  return clause;
218 }
219 
221 {
222  CustomRuleInfo rule;
223 
224  rule.title = tr("Match an exact title");
225  if (!m_baseTitle.isEmpty())
226  rule.description = QString("program.title = '{TITLE}' ");
227  else
228  rule.description = "program.title = 'Nova' ";
230  QVariant::fromValue(rule));
231 
232  if (!m_pginfo->GetSeriesID().isEmpty())
233  {
234  rule.title = tr("Match this series");
235  rule.subtitle.clear();
236  rule.description = QString("program.seriesid = '{SERIESID}' ");
238  QVariant::fromValue(rule));
239  }
240 
241  rule.title = tr("Match words in the title");
242  rule.subtitle.clear();
243  if (!m_pginfo->GetTitle().isEmpty())
244  rule.description = QString("program.title LIKE '%{TITLE}%' ");
245  else
246  rule.description = "program.title LIKE 'CSI: %' ";
248  QVariant::fromValue(rule));
249 
250  rule.title = tr("Match words in the subtitle");
251  rule.subtitle.clear();
252  if (!m_pginfo->GetSubtitle().isEmpty())
253  rule.description = QString("program.subtitle LIKE '%{SUBTITLE}%' ");
254  else
255  rule.description = "program.subtitle LIKE '%Las Vegas%' ";
257  QVariant::fromValue(rule));
258 
259  if (!m_pginfo->GetProgramID().isEmpty())
260  {
261  rule.title = tr("Match this episode");
262  rule.subtitle.clear();
263  rule.description = QString("program.programid = '{PROGID}' ");
264  }
265  else if (!m_pginfo->GetSubtitle().isEmpty())
266  {
267  rule.title = tr("Match this episode");
268  rule.subtitle.clear();
269  rule.description = QString("program.subtitle = '{SUBTITLE}' \n"
270  "AND program.description = '{DESCR}' ");
271  }
272  else
273  {
274  rule.title = tr("Match an exact episode");
275  rule.subtitle.clear();
276  rule.description = QString("program.title = 'Seinfeld' \n"
277  "AND program.subtitle = 'The Soup' ");
278  }
280  QVariant::fromValue(rule));
281 
282  rule.title = tr("Match in any descriptive field");
283  rule.subtitle.clear();
284  rule.description = QString("(program.title LIKE '%Japan%' \n"
285  " OR program.subtitle LIKE '%Japan%' \n"
286  " OR program.description LIKE '%Japan%') ");
288  QVariant::fromValue(rule));
289 
290  rule.title = tr("New episodes only");
291  rule.subtitle.clear();
292  rule.description = "program.previouslyshown = 0 ";
294  QVariant::fromValue(rule));
295 
296  rule.title = tr("Exclude unidentified episodes");
297  rule.subtitle.clear();
298  rule.description = "program.generic = 0 ";
300  QVariant::fromValue(rule));
301 
302  rule.title = tr("First showing of each episode");
303  rule.subtitle.clear();
304  rule.description = "program.first > 0 ";
306  QVariant::fromValue(rule));
307 
308  rule.title = tr("Last showing of each episode");
309  rule.subtitle.clear();
310  rule.description = "program.last > 0 ";
312  QVariant::fromValue(rule));
313 
314  rule.title = tr("Anytime on a specific day of the week");
315  rule.subtitle.clear();
316  rule.description =
317  "DAYNAME(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) = '{DAYNAME}' ";
319  QVariant::fromValue(rule));
320 
321  rule.title = tr("Only on weekdays (Monday through Friday)");
322  rule.subtitle.clear();
323  rule.description =
324  "WEEKDAY(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) < 5 ";
326  QVariant::fromValue(rule));
327 
328  rule.title = tr("Only on weekends");
329  rule.subtitle.clear();
330  rule.description =
331  "WEEKDAY(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) >= 5 ";
333  QVariant::fromValue(rule));
334 
335  rule.title = tr("Only in prime time");
336  rule.subtitle.clear();
337  rule.description =
338  "HOUR(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) >= 19 "
339  "AND HOUR(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) < 23 ";
341  QVariant::fromValue(rule));
342 
343  rule.title = tr("Not in prime time");
344  rule.subtitle.clear();
345  rule.description =
346  "(HOUR(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) < 19 "
347  " OR HOUR(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) >= 23) ";
349  QVariant::fromValue(rule));
350 
351  rule.title = tr("Only on a specific station");
352  rule.subtitle.clear();
353  if (!m_pginfo->GetChannelSchedulingID().isEmpty())
354  rule.description = QString("channel.callsign = '{SCHEDID}' ");
355  else
356  rule.description = "channel.callsign = 'ESPN' ";
358  QVariant::fromValue(rule));
359 
360  rule.title = tr("Exclude one station");
361  rule.subtitle.clear();
362  rule.description = "channel.callsign != 'GOLF' ";
364  QVariant::fromValue(rule));
365 
366  rule.title = tr("Match related callsigns");
367  rule.subtitle.clear();
368  rule.description = "channel.callsign LIKE 'HBO%' ";
370  QVariant::fromValue(rule));
371 
372  rule.title = tr("Only channels from the Favorites group");
373  rule.subtitle = ", channelgroup cg, channelgroupnames cgn";
374  rule.description = "cgn.name = 'Favorites' \n"
375  "AND cg.grpid = cgn.grpid \n"
376  "AND program.chanid = cg.chanid ";
378  QVariant::fromValue(rule));
379 
380  rule.title = tr("Only channels from a specific video source");
381  rule.subtitle.clear();
382  rule.description = "channel.sourceid = 2 ";
384  QVariant::fromValue(rule));
385 
386  rule.title = tr("Only channels marked as commercial free");
387  rule.subtitle.clear();
388  rule.description = QString("channel.commmethod = %1 ")
389  .arg(COMM_DETECT_COMMFREE);
391  QVariant::fromValue(rule));
392 
393  rule.title = tr("Only shows marked as HDTV");
394  rule.subtitle.clear();
395  rule.description = "program.hdtv > 0 ";
397  QVariant::fromValue(rule));
398 
399  rule.title = tr("Only shows marked as widescreen");
400  rule.subtitle.clear();
401  rule.description = "FIND_IN_SET('WIDESCREEN', program.videoprop) > 0 ";
403  QVariant::fromValue(rule));
404 
405  rule.title = tr("Exclude H.264 encoded streams (EIT only)");
406  rule.subtitle.clear();
407  rule.description = "FIND_IN_SET('AVC', program.videoprop) = 0 ";
409  QVariant::fromValue(rule));
410 
411  rule.title = tr("Only shows with in-vision signing");
412  rule.subtitle.clear();
413  rule.description = "FIND_IN_SET('SIGNED', program.subtitletypes) > 0 ";
415  QVariant::fromValue(rule));
416 
417  rule.title = tr("Only shows with in-vision subtitles");
418  rule.subtitle.clear();
419  rule.description = "FIND_IN_SET('ONSCREEN', program.subtitletypes) > 0 ";
421  QVariant::fromValue(rule));
422 
423  rule.title = tr("Limit by category");
424  rule.subtitle.clear();
425  if (!m_pginfo->GetCategory().isEmpty())
426  rule.description = QString("program.category = '{CATEGORY}' ");
427  else
428  rule.description = "program.category = 'Reality' ";
430  QVariant::fromValue(rule));
431 
432  rule.title = tr("All matches for a genre (Schedules Direct)");
433  rule.subtitle = "LEFT JOIN programgenres ON "
434  "program.chanid = programgenres.chanid AND "
435  "program.starttime = programgenres.starttime ";
436  if (!m_pginfo->GetCategory().isEmpty())
437  rule.description = QString("programgenres.genre = '{CATEGORY}' ");
438  else
439  rule.description = "programgenres.genre = 'Reality' ";
441  QVariant::fromValue(rule));
442 
443  rule.title = tr("Limit by MPAA or VCHIP rating (Schedules Direct)");
444  rule.subtitle = "LEFT JOIN programrating ON "
445  "program.chanid = programrating.chanid AND "
446  "program.starttime = programrating.starttime ";
447  rule.description = "(programrating.rating = 'G' OR programrating.rating "
448  "LIKE 'TV-Y%') ";
450  QVariant::fromValue(rule));
451 
452  rule.title = tr("Category type (%1)", "List of hardcoded category types")
453  .arg("'movie', 'series', 'sports', 'tvshow'");
454  rule.subtitle.clear();
455  rule.description = "program.category_type = 'sports' ";
457  QVariant::fromValue(rule));
458 
459  rule.title = tr("Limit movies by the year of release");
460  rule.subtitle.clear();
461  rule.description = "program.category_type = 'movie' AND "
462  "program.airdate >= 2000 ";
464  QVariant::fromValue(rule));
465 
466  rule.title = tr("Minimum star rating (0.0 to 1.0 for movies only)");
467  rule.subtitle.clear();
468  rule.description = "program.stars >= 0.75 ";
470  QVariant::fromValue(rule));
471 
472  rule.title = tr("Person named in the credits (Schedules Direct)");
473  rule.subtitle = ", people, credits";
474  rule.description = "people.name = 'Tom Hanks' \n"
475  "AND credits.person = people.person \n"
476  "AND program.chanid = credits.chanid \n"
477  "AND program.starttime = credits.starttime ";
479  QVariant::fromValue(rule));
480 
481 /* This shows how to use oldprogram but is a bad idea in practice.
482  This would match all future showings until the day after the first
483  showing when all future showing are no longer 'new' titles.
484 
485  rule.title = tr("Only titles from the New Titles list");
486  rule.subtitle = "LEFT JOIN oldprogram"
487  " ON oldprogram.oldtitle = program.title ";
488  rule.description = "oldprogram.oldtitle IS NULL ";
489  new MythUIButtonListItem(m_clauseList, rule.title,
490  QVariant::fromValue(rule));
491 */
492 
493  rule.title = tr("Re-record SDTV in HDTV (disable duplicate matching)");
494  rule.subtitle = ", recordedprogram rp1 LEFT OUTER JOIN recordedprogram rp2"
495  " ON rp1.programid = rp2.programid AND rp2.hdtv = 1";
496  rule.description = "program.programid = rp1.programid \n"
497  "AND rp1.hdtv = 0 \n"
498  "AND program.hdtv = 1 \n"
499  "AND rp2.starttime IS NULL ";
501  QVariant::fromValue(rule));
502 
503  rule.title = tr("Multiple sports teams (complete example)");
504  rule.subtitle.clear();
505  rule.description = "program.title = 'NBA Basketball' \n"
506  "AND program.subtitle REGEXP '(Miami|Cavaliers|Lakers)' \n"
507  "AND program.first > 0 \n";
509  QVariant::fromValue(rule));
510 
511  rule.title = tr("Sci-fi B-movies (complete example)");
512  rule.subtitle.clear();
513  rule.description = "program.category_type='movie' \n"
514  "AND program.category='Science fiction' \n"
515  "AND program.stars <= 0.5 AND program.airdate < 1970 ";
517  QVariant::fromValue(rule));
518 
519  rule.title =
520  tr("SportsCenter Overnight (complete example - use FindDaily)");
521  rule.subtitle.clear();
522  rule.description =
523  "program.title = 'SportsCenter' \n"
524  "AND HOUR(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) >= 2 \n"
525  "AND HOUR(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) <= 6 ";
527  QVariant::fromValue(rule));
528 
529  rule.title = tr("Movie of the Week (complete example - use FindWeekly)");
530  rule.subtitle.clear();
531  rule.description =
532  "program.category_type='movie' \n"
533  "AND program.stars >= 1.0 AND program.airdate >= 1965 \n"
534  "AND DAYNAME(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) = 'Friday' \n"
535  "AND HOUR(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) >= 12 ";
537  QVariant::fromValue(rule));
538 
539  rule.title = tr("First Episodes (complete example for Schedules Direct)");
540  rule.subtitle.clear();
541  rule.description = "program.first > 0 \n"
542  "AND program.programid LIKE 'EP%0001' \n"
543  "AND program.originalairdate = "
544  "DATE(CONVERT_TZ(program.starttime, 'Etc/UTC', 'SYSTEM')) ";
546  QVariant::fromValue(rule));
547 
549 
550  MSqlQuery result(MSqlQuery::InitCon());
551  result.prepare("SELECT rulename,fromclause,whereclause,search "
552  "FROM customexample;");
553 
554  if (result.exec())
555  {
556  while (result.next())
557  {
558  QString str = result.value(0).toString();
559 
560  if (result.value(3).toInt() > 0)
561  str += m_seSuffix;
562  else
563  str += m_exSuffix;
564 
565  rule.title = str;
566  rule.subtitle = result.value(1).toString();
567  rule.description = result.value(2).toString();
569  QVariant::fromValue(rule));
570  }
571  }
572 }
573 
575 {
576  if (!item || item == m_currentRuleItem)
577  return;
578 
579  m_currentRuleItem = item;
580 
581  auto rule = item->GetData().value<CustomRuleInfo>();
582 
583  m_titleEdit->SetText(rule.title);
584  m_descriptionEdit->SetText(rule.description);
585  m_subtitleEdit->SetText(rule.subtitle);
586 
587  textChanged();
588 }
589 
591 {
592  bool hastitle = !m_titleEdit->GetText().isEmpty();
593  bool hasdesc = !m_descriptionEdit->GetText().isEmpty();
594 
595  m_testButton->SetEnabled(hasdesc);
596  m_recordButton->SetEnabled(hastitle && hasdesc);
598  (hastitle && hasdesc));
599 }
600 
602 {
603  if (!item)
604  return;
605 
606  auto rule = item->GetData().value<CustomRuleInfo>();
607 
608  QString msg = (m_evaluate) ? evaluate(rule.description) : rule.description;
609  msg = QString("\"%1\"").arg(msg.simplified());
610  m_clauseText->SetText(msg);
611 
612  bool hastitle = !m_titleEdit->GetText().isEmpty();
613  bool hasdesc = !m_descriptionEdit->GetText().isEmpty();
614 
616  (hastitle && hasdesc));
617 }
618 
620 {
621  if (!item)
622  return;
623 
624  auto rule = item->GetData().value<CustomRuleInfo>();
625 
626  QString clause;
627  QString desc = m_descriptionEdit->GetText();
628 
629  static const QRegularExpression kNonWhitespaceRE { "\\S" };
630  if (desc.contains(kNonWhitespaceRE))
631  clause = "AND ";
632  clause += (m_evaluate) ? evaluate(rule.description) : rule.description;
633 
634  m_descriptionEdit->SetText(desc.append(clause));
635 
636  QString sub = m_subtitleEdit->GetText();
637  m_subtitleEdit->SetText(sub.append(rule.subtitle));
638 }
639 
641 {
642  if (!checkSyntax())
643  {
644  return;
645  }
646 
648  auto *pl = new ProgLister(mainStack, plSQLSearch,
651  if (pl->Create())
652  {
653  mainStack->AddScreen(pl);
654  }
655  else
656  delete pl;
657 }
658 
673 {
674  if (!checkSyntax())
675  return;
676 
677  auto *record = new RecordingRule();
678 
680  auto rule = item->GetData().value<CustomRuleInfo>();
681 
682  int cur_recid = rule.recordid.toInt();
683  if (cur_recid > 0)
684  {
685  record->ModifyPowerSearchByID(cur_recid, m_titleEdit->GetText(),
688  }
689  else
690  {
691  record->LoadBySearch(kPowerSearch, m_titleEdit->GetText(),
694  m_pginfo->GetTitle().isEmpty() ? nullptr : m_pginfo);
695  }
696 
698  auto *schededit = new ScheduleEditor(mainStack, record);
699  if (schededit->Create())
700  {
701  mainStack->AddScreen(schededit);
702  connect(schededit, &ScheduleEditor::ruleSaved, this, &CustomEdit::scheduleCreated);
703  }
704  else
705  delete schededit;
706 }
707 
709 {
710  if (ruleID > 0)
711  Close();
712 }
713 
715 {
716  bool exampleExists = false;
717 
718  MSqlQuery query(MSqlQuery::InitCon());
719  query.prepare("SELECT rulename,whereclause FROM customexample "
720  "WHERE rulename = :RULE;");
721  query.bindValue(":RULE", m_titleEdit->GetText());
722 
723  if (query.exec() && query.next())
724  exampleExists = true;
725 
726  QString msg = QString("%1: %2\n\n").arg(tr("Current Example"),
727  m_titleEdit->GetText());
728 
729  if (!m_subtitleEdit->GetText().isEmpty())
730  msg += m_subtitleEdit->GetText() + "\n\n";
731 
732  msg += m_descriptionEdit->GetText();
733 
735  auto *storediag = new MythDialogBox(msg, mainStack, "storePopup", true);
736 
737  storediag->SetReturnEvent(this, "storeruledialog");
738  if (storediag->Create())
739  {
740  if (!m_titleEdit->GetText().isEmpty())
741  {
742  QString str;
743  // Keep strings whole for translation!
744  if (exampleExists)
745  str = tr("Replace as a search");
746  else
747  str = tr("Store as a search");
748  storediag->AddButton(str);
749 
750  if (exampleExists)
751  str = tr("Replace as an example");
752  else
753  str = tr("Store as an example");
754  storediag->AddButton(str);
755  }
756 
758  {
760  QString str = QString("%1 \"%2\"").arg(tr("Delete"),
761  item->GetText());
762  storediag->AddButton(str);
763  }
764  mainStack->AddScreen(storediag);
765  }
766  else
767  delete storediag;
768 }
769 
770 
772 {
773  bool ret = false;
774  QString msg;
775 
776  QString desc = evaluate(m_descriptionEdit->GetText());
777  QString from = m_subtitleEdit->GetText();
778  if (desc.contains(RecordingInfo::kReLeadingAnd))
779  {
780  msg = tr("Power Search rules no longer require a leading \"AND\".");
781  }
782  else if (desc.contains(';'))
783  {
784  msg = tr("Power Search rules cannot include semicolon ( ; ) ");
785  msg += tr("statement terminators.");
786  }
787  else
788  {
789  MSqlQuery query(MSqlQuery::InitCon());
790  query.prepare(QString("SELECT NULL FROM (program, channel, oldrecorded AS oldrecstatus) "
791  "%1 WHERE %2 LIMIT 5").arg(from, desc));
792 
793  if (query.exec())
794  {
795  ret = true;
796  }
797  else
798  {
799  msg = tr("An error was found when checking") + ":\n\n";
800  msg += query.executedQuery();
801  msg += "\n\n" + tr("The database error was") + ":\n";
802  msg += query.lastError().databaseText();
803  }
804  }
805 
806  if (!msg.isEmpty())
807  {
808  MythScreenStack *popupStack = GetMythMainWindow()->
809  GetStack("popup stack");
810  auto *checkSyntaxPopup =
811  new MythConfirmationDialog(popupStack, msg, false);
812 
813  if (checkSyntaxPopup->Create())
814  {
815  checkSyntaxPopup->SetReturnEvent(this, "checkSyntaxPopup");
816  popupStack->AddScreen(checkSyntaxPopup);
817  }
818  else
819  {
820  delete checkSyntaxPopup;
821  }
822  ret = false;
823  }
824  return ret;
825 }
826 
827 void CustomEdit::storeRule(bool is_search, bool is_new)
828 {
829  CustomRuleInfo rule;
830  rule.recordid = '0';
831  rule.title = m_titleEdit->GetText();
832  rule.subtitle = m_subtitleEdit->GetText();
834 
835  MSqlQuery query(MSqlQuery::InitCon());
836  query.prepare("REPLACE INTO customexample "
837  "(rulename,fromclause,whereclause,search) "
838  "VALUES(:RULE,:FROMC,:WHEREC,:SEARCH);");
839  query.bindValue(":RULE", rule.title);
840  query.bindValue(":FROMC", rule.subtitle);
841  query.bindValue(":WHEREC", rule.description);
842  query.bindValue(":SEARCH", is_search);
843 
844  if (is_search)
845  rule.title += m_seSuffix;
846  else
847  rule.title += m_exSuffix;
848 
849  if (!query.exec())
850  MythDB::DBError("Store custom example", query);
851  else if (is_new)
852  {
854  QVariant::fromValue(rule));
855  }
856  else
857  {
858  /* Modify the existing entry. We know one exists from the database
859  search but do not know its position in the clause list. It may
860  or may not be the current item. */
861  for (int i = m_maxex; i < m_clauseList->GetCount(); i++)
862  {
864  QString removedStr = item->GetText().remove(m_seSuffix)
865  .remove(m_exSuffix);
866  if (m_titleEdit->GetText() == removedStr)
867  {
868  item->SetData(QVariant::fromValue(rule));
869  clauseChanged(item);
870  break;
871  }
872  }
873  }
874 
875 
876 }
877 
879 {
881  if (!item || m_clauseList->GetCurrentPos() < m_maxex)
882  return;
883 
884  MSqlQuery query(MSqlQuery::InitCon());
885  query.prepare("DELETE FROM customexample "
886  "WHERE rulename = :RULE;");
887  query.bindValue(":RULE", item->GetText().remove(m_seSuffix)
888  .remove(m_exSuffix));
889 
890  if (!query.exec())
891  MythDB::DBError("Delete custom example", query);
892  else
893  {
894  m_clauseList->RemoveItem(item);
895  }
896 }
897 
898 void CustomEdit::customEvent(QEvent *event)
899 {
900  if (event->type() == DialogCompletionEvent::kEventType)
901  {
902  auto *dce = (DialogCompletionEvent*)(event);
903 
904  QString resultid = dce->GetId();
905  QString resulttext = dce->GetResultText();
906 
907  if (resultid == "storeruledialog")
908  {
909  if (resulttext.startsWith(tr("Delete")))
910  {
911  deleteRule();
912  }
913  else if (!resulttext.isEmpty())
914  {
915  storeRule(resulttext.contains(tr("as a search")),
916  !resulttext.startsWith(tr("Replace")));
917  }
918  }
919  }
920 }
921 
922 bool CustomEdit::keyPressEvent(QKeyEvent *event)
923 {
924  if (GetFocusWidget()->keyPressEvent(event))
925  return true;
926 
927  QStringList actions;
928  bool handled = GetMythMainWindow()->TranslateKeyPress("TV Frontend", event, actions);
929 
930  for (int i = 0; i < actions.size() && !handled; i++)
931  {
932  QString action = actions[i];
933  handled = true;
934 
935  if (action == "DELETE")
936  {
937  if (GetFocusWidget() == m_clauseList)
938  deleteRule();
939  // else if (GetFocusWidget() == m_ruleList)
940  // deleteRecordingRule();
941  }
942  else if (action == "EDIT")
943  {
944  // toggle evaluated/unevaluated sample view
947  clauseChanged(item);
948  } else
949  handled = false;
950  }
951 
952  if (!handled && MythScreenType::keyPressEvent(event))
953  handled = true;
954 
955  return handled;
956 }
MythUIButton::Clicked
void Clicked()
scheduleeditor.h
MythUIButtonList::GetItemAt
MythUIButtonListItem * GetItemAt(int pos) const
Definition: mythuibuttonlist.cpp:1673
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:811
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:128
MythMainWindow::GetMainStack
MythScreenStack * GetMainStack()
Definition: mythmainwindow.cpp:318
CustomEdit::m_seSuffix
QString m_seSuffix
Definition: customedit.h:53
MythUITextEdit::SetMaxLength
void SetMaxLength(int length)
Definition: mythuitextedit.cpp:192
MythUIButtonList::GetItemCurrent
MythUIButtonListItem * GetItemCurrent() const
Definition: mythuibuttonlist.cpp:1587
CustomEdit::m_ruleList
MythUIButtonList * m_ruleList
Definition: customedit.h:56
RecordingInfo::kReSearchTypeName
static const QRegularExpression kReSearchTypeName
Definition: recordinginfo.h:197
mythuitext.h
CustomRuleInfo::title
QString title
Definition: customedit.h:78
customedit.h
CustomEdit::storeRule
void storeRule(bool is_search, bool is_new)
Definition: customedit.cpp:827
mythdb.h
CustomEdit::ruleChanged
void ruleChanged(MythUIButtonListItem *item)
Definition: customedit.cpp:574
MythScreenType::Close
virtual void Close()
Definition: mythscreentype.cpp:386
CustomEdit::m_currentRuleItem
const MythUIButtonListItem * m_currentRuleItem
Definition: customedit.h:73
CustomEdit::clauseChanged
void clauseChanged(MythUIButtonListItem *item)
Definition: customedit.cpp:601
MythUIButtonList::RemoveItem
void RemoveItem(MythUIButtonListItem *item)
Definition: mythuibuttonlist.cpp:1485
ProgramInfo::GetChannelName
QString GetChannelName(void) const
This is the channel name in the local market, i.e.
Definition: programinfo.h:386
ProgramInfo::GetChanNum
QString GetChanNum(void) const
This is the channel "number", in the form 1, 1_2, 1-2, 1#1, etc.
Definition: programinfo.h:376
CustomEdit::m_maxex
int m_maxex
Definition: customedit.h:50
MythUIButtonList::itemSelected
void itemSelected(MythUIButtonListItem *item)
CustomEdit::checkSyntax
bool checkSyntax(void)
Definition: customedit.cpp:771
mythdialogbox.h
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:205
MythScreenStack
Definition: mythscreenstack.h:16
ProgramInfo::GetChannelSchedulingID
QString GetChannelSchedulingID(void) const
This is the unique programming identifier of a channel.
Definition: programinfo.h:383
ProgramInfo::GetCategory
QString GetCategory(void) const
Definition: programinfo.h:369
ProgLister
Definition: proglist.h:33
RecordingInfo::kReLeadingAnd
static const QRegularExpression kReLeadingAnd
Definition: recordinginfo.h:200
RecordingRule
Internal representation of a recording rule, mirrors the record table.
Definition: recordingrule.h:28
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:617
CustomEdit::~CustomEdit
~CustomEdit(void) override
Definition: customedit.cpp:37
ProgramInfo::GetScheduledEndTime
QDateTime GetScheduledEndTime(void) const
The scheduled end time of the program.
Definition: programinfo.h:397
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythScreenType
Screen in which all other widgets are contained and rendered.
Definition: mythscreentype.h:45
CustomEdit::m_descriptionEdit
MythUITextEdit * m_descriptionEdit
Definition: customedit.h:62
CustomEdit::m_exSuffix
QString m_exSuffix
Definition: customedit.h:54
CustomEdit::deleteRule
void deleteRule(void)
Definition: customedit.cpp:878
ScheduleEditor::ruleSaved
void ruleSaved(int ruleId)
MythUITextEdit::GetText
QString GetText(void) const
Definition: mythuitextedit.h:50
CustomEdit::customEvent
void customEvent(QEvent *event) override
Definition: customedit.cpp:898
mythuibuttonlist.h
MythUIButtonList::GetCount
int GetCount() const
Definition: mythuibuttonlist.cpp:1652
CustomRuleInfo
Definition: customedit.h:76
CustomEdit::loadClauses
void loadClauses(void)
Definition: customedit.cpp:220
CustomRuleInfo::description
QString description
Definition: customedit.h:80
proglist.h
CustomEdit::m_clauseText
MythUIText * m_clauseText
Definition: customedit.h:67
MythScreenType::GetFocusWidget
MythUIType * GetFocusWidget(void) const
Definition: mythscreentype.cpp:113
CustomEdit::m_titleEdit
MythUITextEdit * m_titleEdit
Definition: customedit.h:59
CustomEdit::textChanged
void textChanged(void)
Definition: customedit.cpp:590
MythObservable::addListener
void addListener(QObject *listener)
Add a listener to the observable.
Definition: mythobservable.cpp:38
MythUIButtonListItem
Definition: mythuibuttonlist.h:41
CustomEdit::CustomEdit
CustomEdit(MythScreenStack *parent, ProgramInfo *m_pginfo=nullptr)
Definition: customedit.cpp:20
MythUITextEdit::SetText
void SetText(const QString &text, bool moveCursor=true)
Definition: mythuitextedit.cpp:197
COMM_DETECT_COMMFREE
@ COMM_DETECT_COMMFREE
Definition: programtypes.h:130
ProgramInfo::GetScheduledStartTime
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
Definition: programinfo.h:390
MythUIButtonList::itemClicked
void itemClicked(MythUIButtonListItem *item)
MythMainWindow::TranslateKeyPress
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
Definition: mythmainwindow.cpp:1111
kPowerSearch
@ kPowerSearch
Definition: recordingtypes.h:81
MythUIButtonList::GetCurrentPos
int GetCurrentPos() const
Definition: mythuibuttonlist.h:238
CustomRuleInfo::recordid
QString recordid
Definition: customedit.h:77
MythDialogBox
Basic menu dialog, message and a list of options.
Definition: mythdialogbox.h:166
CustomEdit::m_baseTitle
QString m_baseTitle
Definition: customedit.h:48
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:549
ProgramInfo::GetTitle
QString GetTitle(void) const
Definition: programinfo.h:361
ProgramInfo::GetDescription
QString GetDescription(void) const
Definition: programinfo.h:365
MythDB::DBError
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
MythScreenType::BuildFocusList
void BuildFocusList(void)
Definition: mythscreentype.cpp:206
CustomEdit::m_testButton
MythUIButton * m_testButton
Definition: customedit.h:68
MythUIType::SetEnabled
void SetEnabled(bool enable)
Definition: mythuitype.cpp:1131
CustomEdit::m_cancelButton
MythUIButton * m_cancelButton
Definition: customedit.h:71
MythUIButtonListItem::GetData
QVariant GetData()
Definition: mythuibuttonlist.cpp:3665
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
CustomEdit::keyPressEvent
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
Definition: customedit.cpp:922
ProgramInfo::GetSeriesID
QString GetSeriesID(void) const
Definition: programinfo.h:435
UIUtilDisp::Assign
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
Definition: mythuiutils.h:27
MSqlQuery::lastError
QSqlError lastError(void) const
Definition: mythdbcon.h:214
MythUIButtonListItem::GetText
QString GetText(const QString &name="") const
Definition: mythuibuttonlist.cpp:3315
CustomEdit::testClicked
void testClicked(void)
Definition: customedit.cpp:640
CustomEdit::Create
bool Create() override
Definition: customedit.cpp:44
ProgramInfo::GetChanID
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:372
ProgramInfo::GetEpisode
uint GetEpisode(void) const
Definition: programinfo.h:367
ProgramInfo
Holds information on recordings and videos.
Definition: programinfo.h:67
MythScreenType::keyPressEvent
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
Definition: mythscreentype.cpp:404
MythConfirmationDialog
Dialog asking for user confirmation. Ok and optional Cancel button.
Definition: mythdialogbox.h:272
CustomEdit::m_pginfo
ProgramInfo * m_pginfo
Definition: customedit.h:47
ProgramInfo::GetSeason
uint GetSeason(void) const
Definition: programinfo.h:366
mythcorecontext.h
MSqlQuery::executedQuery
QString executedQuery(void) const
Definition: mythdbcon.h:206
mythuitextedit.h
XMLParseBase::LoadWindowFromXML
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
Definition: xmlparsebase.cpp:695
CustomEdit::m_storeButton
MythUIButton * m_storeButton
Definition: customedit.h:70
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:887
DialogCompletionEvent
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:41
CustomEdit::clauseClicked
void clauseClicked(MythUIButtonListItem *item)
Definition: customedit.cpp:619
MythUIText::SetText
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:132
DialogCompletionEvent::kEventType
static const Type kEventType
Definition: mythdialogbox.h:57
GetMythMainWindow
MythMainWindow * GetMythMainWindow(void)
Definition: mythmainwindow.cpp:104
MythUIButtonList::SetItemCurrent
void SetItemCurrent(MythUIButtonListItem *item)
Definition: mythuibuttonlist.cpp:1554
build_compdb.action
action
Definition: build_compdb.py:9
CustomEdit::recordClicked
void recordClicked(void)
The user clicked on the 'Record' button in the 'Custom Edit' window.
Definition: customedit.cpp:672
mythuibutton.h
MythUITextEdit::valueChanged
void valueChanged()
plSQLSearch
@ plSQLSearch
Definition: proglist.h:22
CustomRuleInfo::subtitle
QString subtitle
Definition: customedit.h:79
ProgramInfo::GetProgramID
QString GetProgramID(void) const
Definition: programinfo.h:436
ProgramInfo::GetRecordingRuleID
uint GetRecordingRuleID(void) const
Definition: programinfo.h:449
CustomEdit::m_evaluate
bool m_evaluate
Definition: customedit.h:51
recordingrule.h
MythUIButtonListItem::SetData
void SetData(QVariant data)
Definition: mythuibuttonlist.cpp:3660
MythScreenStack::AddScreen
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Definition: mythscreenstack.cpp:52
ScheduleEditor
Construct a recording schedule.
Definition: scheduleeditor.h:143
CustomEdit::storeClicked
void storeClicked(void)
Definition: customedit.cpp:714
CustomEdit::evaluate
QString evaluate(QString clause)
Definition: customedit.cpp:148
MythObservable::removeListener
void removeListener(QObject *listener)
Remove a listener to the observable.
Definition: mythobservable.cpp:55
CustomEdit::m_clauseList
MythUIButtonList * m_clauseList
Definition: customedit.h:57
CustomEdit::m_subtitleEdit
MythUITextEdit * m_subtitleEdit
Definition: customedit.h:65
CustomEdit::loadData
void loadData(void)
Definition: customedit.cpp:95
CustomEdit::m_recordButton
MythUIButton * m_recordButton
Definition: customedit.h:69
CustomEdit::scheduleCreated
void scheduleCreated(int ruleID)
Definition: customedit.cpp:708
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:836
ProgramInfo::GetSubtitle
QString GetSubtitle(void) const
Definition: programinfo.h:363