MythTV  master
customedit.cpp
Go to the documentation of this file.
1 
2 #include "customedit.h"
3 
4 // QT
5 #include <QSqlError>
6 
7 // libmythbase
8 #include "mythdb.h"
9 
10 // libmyth
11 #include "mythcorecontext.h"
12 
13 // libmythui
14 #include "mythuibuttonlist.h"
15 #include "mythuitextedit.h"
16 #include "mythuibutton.h"
17 #include "mythdialogbox.h"
18 #include "mythuitext.h"
19 
20 // libmythtv
21 #include "recordingrule.h"
22 
23 // mythfrontend
24 #include "scheduleeditor.h"
25 #include "proglist.h"
26 
28  : MythScreenType(parent, "CustomEdit")
29 {
30  if (pginfo)
31  m_pginfo = new ProgramInfo(*pginfo);
32  else
33  m_pginfo = new ProgramInfo();
34 
36  m_baseTitle.remove(QRegExp(" \\(.*\\)$"));
37 
38  m_seSuffix = QString(" (%1)").arg(tr("stored search"));
39  m_exSuffix = QString(" (%1)").arg(tr("stored example"));
40 
42 }
43 
45 {
46  delete m_pginfo;
47 
49 }
50 
52 {
53  if (!LoadWindowFromXML("schedule-ui.xml", "customedit", this))
54  return false;
55 
56  bool err = false;
57  UIUtilE::Assign(this, m_ruleList, "rules", &err);
58  UIUtilE::Assign(this, m_clauseList, "clauses", &err);
59 
60  UIUtilE::Assign(this, m_titleEdit, "title", &err);
61  UIUtilE::Assign(this, m_subtitleEdit, "subtitle", &err);
62  UIUtilE::Assign(this, m_descriptionEdit, "description", &err);
63  UIUtilE::Assign(this, m_clauseText, "clausetext", &err);
64  UIUtilE::Assign(this, m_testButton, "test", &err);
65  UIUtilE::Assign(this, m_recordButton, "record", &err);
66  UIUtilE::Assign(this, m_storeButton, "store", &err);
67  UIUtilE::Assign(this, m_cancelButton, "cancel", &err);
68 
69  if (err)
70  {
71  LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'customedit'");
72  return false;
73  }
74 
75  connect(m_ruleList, SIGNAL(itemSelected(MythUIButtonListItem *)),
77  connect(m_titleEdit, SIGNAL(valueChanged(void)), this,
78  SLOT(textChanged(void)));
81  connect(m_descriptionEdit, SIGNAL(valueChanged(void)), this,
82  SLOT(textChanged(void)));
84 
85  connect(m_clauseList, SIGNAL(itemSelected(MythUIButtonListItem *)),
87  connect(m_clauseList, SIGNAL(itemClicked(MythUIButtonListItem *)),
89 
90  connect(m_testButton, SIGNAL(Clicked()), SLOT(testClicked()));
91  connect(m_recordButton, SIGNAL(Clicked()), SLOT(recordClicked()));
92  connect(m_storeButton, SIGNAL(Clicked()), SLOT(storeClicked()));
93  connect(m_cancelButton, SIGNAL(Clicked()), SLOT(Close()));
94 
95  loadData();
97 
98  return true;
99 }
100 
101 
103 {
104  CustomRuleInfo rule;
105 
106  // New Rule defaults
107  rule.recordid = '0';
108  rule.title = m_baseTitle;
109  if (!m_baseTitle.isEmpty())
110  {
111  QString quoteTitle = m_baseTitle;
112  quoteTitle.replace("\'","\'\'");
113  rule.description = QString("program.title = '%1' ").arg(quoteTitle);
114  }
115 
116  new MythUIButtonListItem(m_ruleList, tr("<New rule>"),
117  qVariantFromValue(rule));
118 
119  MSqlQuery result(MSqlQuery::InitCon());
120  result.prepare("SELECT recordid, title, subtitle, description "
121  "FROM record WHERE search = :SEARCH ORDER BY title;");
122  result.bindValue(":SEARCH", kPowerSearch);
123 
124  if (result.exec())
125  {
126  while (result.next())
127  {
128  QString trimTitle = result.value(1).toString();
129  trimTitle.remove(QRegExp(" \\(.*\\)$"));
130 
131  rule.recordid = result.value(0).toString();
132  rule.title = trimTitle;
133  rule.subtitle = result.value(2).toString();
134  rule.description = result.value(3).toString();
135 
136  // No memory leak. MythUIButtonListItem adds the new item
137  // into m_ruleList.
138  auto *item = new MythUIButtonListItem(m_ruleList, rule.title,
139  qVariantFromValue(rule));
140 
141  if (trimTitle == m_baseTitle ||
142  result.value(0).toUInt() == m_pginfo->GetRecordingRuleID())
143  m_ruleList->SetItemCurrent(item);
144  }
145  }
146  else
147  MythDB::DBError("Get power search rules query", result);
148 
149  loadClauses();
150 
151  textChanged();
152 }
153 
154 QString CustomEdit::evaluate(QString clause)
155 {
156  int e0=0;
157  while (true) {
158  int s0 = clause.indexOf (QRegExp("\\{[A-Z]+\\}"), e0);
159 
160  if (s0 < 0)
161  break;
162 
163  e0 = clause.indexOf ("}", s0);
164 
165  QString mid = clause.mid(s0 + 1, e0 - s0 - 1);
166  QString repl = "";
167 
168  if (!mid.compare("TITLE")) {
169  repl = m_pginfo->GetTitle();
170  repl.replace("\'","\'\'");
171  } else if (!mid.compare("SUBTITLE")) {
172  repl = m_pginfo->GetSubtitle();
173  repl.replace("\'","\'\'");
174  } else if (!mid.compare("DESCR")) {
175  repl = m_pginfo->GetDescription();
176  repl.replace("\'","\'\'");
177  } else if (!mid.compare("SERIESID")) {
178  repl = QString("%1").arg(m_pginfo->GetSeriesID());
179  } else if (!mid.compare("PROGID")) {
180  repl = m_pginfo->GetProgramID();
181  } else if (!mid.compare("SEASON")) {
182  repl = QString::number(m_pginfo->GetSeason());
183  } else if (!mid.compare("EPISODE")) {
184  repl = QString::number(m_pginfo->GetEpisode());
185  } else if (!mid.compare("CATEGORY")) {
186  repl = m_pginfo->GetCategory();
187  } else if (!mid.compare("CHANID")) {
188  repl = QString("%1").arg(m_pginfo->GetChanID());
189  } else if (!mid.compare("CHANNUM")) {
190  repl = m_pginfo->GetChanNum();
191  } else if (!mid.compare("SCHEDID")) {
193  } else if (!mid.compare("CHANNAME")) {
194  repl = m_pginfo->GetChannelName();
195  } else if (!mid.compare("DAYNAME")) {
196  repl = m_pginfo->GetScheduledStartTime().toString("dddd");
197  } else if (!mid.compare("STARTDATE")) {
198  repl = m_pginfo->GetScheduledStartTime().toString("yyyy-mm-dd hh:mm:ss");
199  } else if (!mid.compare("ENDDATE")) {
200  repl = m_pginfo->GetScheduledEndTime().toString("yyyy-mm-dd hh:mm:ss");
201  } else if (!mid.compare("STARTTIME")) {
202  repl = m_pginfo->GetScheduledStartTime().toString("hh:mm");
203  } else if (!mid.compare("ENDTIME")) {
204  repl = m_pginfo->GetScheduledEndTime().toString("hh:mm");
205  } else if (!mid.compare("STARTSEC")) {
206  QDateTime date = m_pginfo->GetScheduledStartTime();
207  QDateTime midnight = QDateTime(date.date());
208  repl = QString("%1").arg(midnight.secsTo(date));
209  } else if (!mid.compare("ENDSEC")) {
210  QDateTime date = m_pginfo->GetScheduledEndTime();
211  QDateTime midnight = QDateTime(date.date());
212  repl = QString("%1").arg(midnight.secsTo(date));
213  }
214  // unknown tags are simply removed
215  clause.replace(s0, e0 - s0 + 1, 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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(rule));
289 
290  rule.title = tr("New episodes only");
291  rule.subtitle.clear();
292  rule.description = "program.previouslyshown = 0 ";
294  qVariantFromValue(rule));
295 
296  rule.title = tr("Exclude unidentified episodes");
297  rule.subtitle.clear();
298  rule.description = "program.generic = 0 ";
300  qVariantFromValue(rule));
301 
302  rule.title = tr("First showing of each episode");
303  rule.subtitle.clear();
304  rule.description = "program.first > 0 ";
306  qVariantFromValue(rule));
307 
308  rule.title = tr("Last showing of each episode");
309  rule.subtitle.clear();
310  rule.description = "program.last > 0 ";
312  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(rule));
359 
360  rule.title = tr("Exclude one station");
361  rule.subtitle.clear();
362  rule.description = "channel.callsign != 'GOLF' ";
364  qVariantFromValue(rule));
365 
366  rule.title = tr("Match related callsigns");
367  rule.subtitle.clear();
368  rule.description = "channel.callsign LIKE 'HBO%' ";
370  qVariantFromValue(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  qVariantFromValue(rule));
379 
380  rule.title = tr("Only channels from a specific video source");
381  rule.subtitle.clear();
382  rule.description = "channel.sourceid = 2 ";
384  qVariantFromValue(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  qVariantFromValue(rule));
392 
393  rule.title = tr("Only shows marked as HDTV");
394  rule.subtitle.clear();
395  rule.description = "program.hdtv > 0 ";
397  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(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  qVariantFromValue(rule));
570  }
571  }
572 }
573 
575 {
576  if (!item || item == m_currentRuleItem)
577  return;
578 
579  m_currentRuleItem = item;
580 
581  CustomRuleInfo rule = item->GetData().value<CustomRuleInfo>();
582 
583  m_titleEdit->SetText(rule.title);
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  CustomRuleInfo rule = item->GetData().value<CustomRuleInfo>();
607 
608  QString msg = (m_evaluate) ? evaluate(rule.description) : rule.description;
609  msg.replace('\n', ' ');
610  msg.replace(QRegExp(" [ ]*"), " ");
611  msg = QString("\"%1\"").arg(msg);
612  m_clauseText->SetText(msg);
613 
614  bool hastitle = !m_titleEdit->GetText().isEmpty();
615  bool hasdesc = !m_descriptionEdit->GetText().isEmpty();
616 
618  (hastitle && hasdesc));
619 }
620 
622 {
623  if (!item)
624  return;
625 
626  CustomRuleInfo rule = item->GetData().value<CustomRuleInfo>();
627 
628  QString clause;
629  QString desc = m_descriptionEdit->GetText();
630 
631  if (desc.contains(QRegExp("\\S")))
632  clause = "AND ";
633  clause += (m_evaluate) ? evaluate(rule.description) : rule.description;
634 
635  m_descriptionEdit->SetText(desc.append(clause));
636 
637  QString sub = m_subtitleEdit->GetText();
638  m_subtitleEdit->SetText(sub.append(rule.subtitle));
639 }
640 
642 {
643  if (!checkSyntax())
644  {
645  return;
646  }
647 
649  auto *pl = new ProgLister(mainStack, plSQLSearch,
652  if (pl->Create())
653  {
654  mainStack->AddScreen(pl);
655  }
656  else
657  delete pl;
658 }
659 
674 {
675  if (!checkSyntax())
676  return;
677 
678  auto *record = new RecordingRule();
679 
681  CustomRuleInfo rule = item->GetData().value<CustomRuleInfo>();
682 
683  int cur_recid = rule.recordid.toInt();
684  if (cur_recid > 0)
685  {
686  record->ModifyPowerSearchByID(cur_recid, m_titleEdit->GetText(),
689  }
690  else
691  {
692  record->LoadBySearch(kPowerSearch, m_titleEdit->GetText(),
695  m_pginfo->GetTitle().isEmpty() ? nullptr : m_pginfo);
696  }
697 
699  auto *schededit = new ScheduleEditor(mainStack, record);
700  if (schededit->Create())
701  {
702  mainStack->AddScreen(schededit);
703  connect(schededit, SIGNAL(ruleSaved(int)), SLOT(scheduleCreated(int)));
704  }
705  else
706  delete schededit;
707 }
708 
710 {
711  if (ruleID > 0)
712  Close();
713 }
714 
716 {
717  bool exampleExists = false;
718 
719  MSqlQuery query(MSqlQuery::InitCon());
720  query.prepare("SELECT rulename,whereclause FROM customexample "
721  "WHERE rulename = :RULE;");
722  query.bindValue(":RULE", m_titleEdit->GetText());
723 
724  if (query.exec() && query.next())
725  exampleExists = true;
726 
727  QString msg = QString("%1: %2\n\n").arg(tr("Current Example"))
728  .arg(m_titleEdit->GetText());
729 
730  if (!m_subtitleEdit->GetText().isEmpty())
731  msg += m_subtitleEdit->GetText() + "\n\n";
732 
733  msg += m_descriptionEdit->GetText();
734 
736  auto *storediag = new MythDialogBox(msg, mainStack, "storePopup", true);
737 
738  storediag->SetReturnEvent(this, "storeruledialog");
739  if (storediag->Create())
740  {
741  if (!m_titleEdit->GetText().isEmpty())
742  {
743  QString str;
744  // Keep strings whole for translation!
745  if (exampleExists)
746  str = tr("Replace as a search");
747  else
748  str = tr("Store as a search");
749  storediag->AddButton(str);
750 
751  if (exampleExists)
752  str = tr("Replace as an example");
753  else
754  str = tr("Store as an example");
755  storediag->AddButton(str);
756  }
757 
759  {
761  QString str = QString("%1 \"%2\"").arg(tr("Delete"))
762  .arg(item->GetText());
763  storediag->AddButton(str);
764  }
765  mainStack->AddScreen(storediag);
766  }
767  else
768  delete storediag;
769 }
770 
771 
773 {
774  bool ret = false;
775  QString msg;
776 
777  QString desc = evaluate(m_descriptionEdit->GetText());
778  QString from = m_subtitleEdit->GetText();
779  if (desc.contains(QRegExp("^\\s*AND\\s", Qt::CaseInsensitive)))
780  {
781  msg = tr("Power Search rules no longer require a leading \"AND\".");
782  }
783  else if (desc.contains(';'))
784  {
785  msg = tr("Power Search rules cannot include semicolon ( ; ) ");
786  msg += tr("statement terminators.");
787  }
788  else
789  {
790  MSqlQuery query(MSqlQuery::InitCon());
791  query.prepare(QString("SELECT NULL FROM (program,channel) "
792  "%1 WHERE\n%2").arg(from).arg(desc));
793 
794  if (query.exec())
795  {
796  ret = true;
797  }
798  else
799  {
800  msg = tr("An error was found when checking") + ":\n\n";
801  msg += query.executedQuery();
802  msg += "\n\n" + tr("The database error was") + ":\n";
803  msg += query.lastError().databaseText();
804  }
805  }
806 
807  if (!msg.isEmpty())
808  {
809  MythScreenStack *popupStack = GetMythMainWindow()->
810  GetStack("popup stack");
811  auto *checkSyntaxPopup =
812  new MythConfirmationDialog(popupStack, msg, false);
813 
814  if (checkSyntaxPopup->Create())
815  {
816  checkSyntaxPopup->SetReturnEvent(this, "checkSyntaxPopup");
817  popupStack->AddScreen(checkSyntaxPopup);
818  }
819  else
820  {
821  delete checkSyntaxPopup;
822  }
823  ret = false;
824  }
825  return ret;
826 }
827 
828 void CustomEdit::storeRule(bool is_search, bool is_new)
829 {
830  CustomRuleInfo rule;
831  rule.recordid = '0';
832  rule.title = m_titleEdit->GetText();
833  rule.subtitle = m_subtitleEdit->GetText();
835 
836  MSqlQuery query(MSqlQuery::InitCon());
837  query.prepare("REPLACE INTO customexample "
838  "(rulename,fromclause,whereclause,search) "
839  "VALUES(:RULE,:FROMC,:WHEREC,:SEARCH);");
840  query.bindValue(":RULE", rule.title);
841  query.bindValue(":FROMC", rule.subtitle);
842  query.bindValue(":WHEREC", rule.description);
843  query.bindValue(":SEARCH", is_search);
844 
845  if (is_search)
846  rule.title += m_seSuffix;
847  else
848  rule.title += m_exSuffix;
849 
850  if (!query.exec())
851  MythDB::DBError("Store custom example", query);
852  else if (is_new)
853  {
855  qVariantFromValue(rule));
856  }
857  else
858  {
859  /* Modify the existing entry. We know one exists from the database
860  search but do not know its position in the clause list. It may
861  or may not be the current item. */
862  for (int i = m_maxex; i < m_clauseList->GetCount(); i++)
863  {
865  QString removedStr = item->GetText().remove(m_seSuffix)
866  .remove(m_exSuffix);
867  if (m_titleEdit->GetText() == removedStr)
868  {
869  item->SetData(qVariantFromValue(rule));
870  clauseChanged(item);
871  break;
872  }
873  }
874  }
875 
876 
877 }
878 
880 {
882  if (!item || m_clauseList->GetCurrentPos() < m_maxex)
883  return;
884 
885  MSqlQuery query(MSqlQuery::InitCon());
886  query.prepare("DELETE FROM customexample "
887  "WHERE rulename = :RULE;");
888  query.bindValue(":RULE", item->GetText().remove(m_seSuffix)
889  .remove(m_exSuffix));
890 
891  if (!query.exec())
892  MythDB::DBError("Delete custom example", query);
893  else
894  {
895  m_clauseList->RemoveItem(item);
896  }
897 }
898 
899 void CustomEdit::customEvent(QEvent *event)
900 {
901  if (event->type() == DialogCompletionEvent::kEventType)
902  {
903  auto *dce = (DialogCompletionEvent*)(event);
904 
905  QString resultid = dce->GetId();
906  QString resulttext = dce->GetResultText();
907 
908  if (resultid == "storeruledialog")
909  {
910  if (resulttext.startsWith(tr("Delete")))
911  {
912  deleteRule();
913  }
914  else if (!resulttext.isEmpty())
915  {
916  storeRule(resulttext.contains(tr("as a search")),
917  !resulttext.startsWith(tr("Replace")));
918  }
919  }
920  }
921 }
922 
923 bool CustomEdit::keyPressEvent(QKeyEvent *event)
924 {
925  if (GetFocusWidget()->keyPressEvent(event))
926  return true;
927 
928  QStringList actions;
929  bool handled = GetMythMainWindow()->TranslateKeyPress("TV Frontend", event, actions);
930 
931  for (int i = 0; i < actions.size() && !handled; i++)
932  {
933  QString action = actions[i];
934  handled = true;
935 
936  if (action == "DELETE")
937  {
938  if (GetFocusWidget() == m_clauseList)
939  deleteRule();
940  // else if (GetFocusWidget() == m_ruleList)
941  // deleteRecordingRule();
942  }
943  else if (action == "EDIT")
944  {
945  // toggle evaluated/unevaluated sample view
948  clauseChanged(item);
949  } else
950  handled = false;
951  }
952 
953  if (!handled && MythScreenType::keyPressEvent(event))
954  handled = true;
955 
956  return handled;
957 }
QString m_baseTitle
Definition: customedit.h:48
void testClicked(void)
Definition: customedit.cpp:641
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:782
CustomEdit(MythScreenStack *parent, ProgramInfo *m_pginfo=nullptr)
Definition: customedit.cpp:27
MythUIButton * m_testButton
Definition: customedit.h:68
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:863
~CustomEdit(void)
Definition: customedit.cpp:44
Dialog asking for user confirmation.
QString GetChannelName(void) const
This is the channel name in the local market, i.e.
Definition: programinfo.h:378
bool TranslateKeyPress(const QString &context, QKeyEvent *e, QStringList &actions, bool allowJumps=true)
Get a list of actions for a keypress in the given context.
void SetEnabled(bool enable)
QString description
Definition: customedit.h:80
void clauseClicked(MythUIButtonListItem *item)
Definition: customedit.cpp:621
QString recordid
Definition: customedit.h:77
void SetData(QVariant data)
ProgramInfo * m_pginfo
Definition: customedit.h:47
void removeListener(QObject *listener)
Remove a listener to the observable.
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
Basic menu dialog, message and a list of options.
MythUIButton * m_cancelButton
Definition: customedit.h:71
QString GetTitle(void) const
Definition: programinfo.h:353
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:135
bool Create() override
Definition: customedit.cpp:51
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
void recordClicked(void)
The user clicked on the 'Record' button in the 'Custom Edit' window.
Definition: customedit.cpp:673
MythScreenStack * GetMainStack()
void addListener(QObject *listener)
Add a listener to the observable.
MythUIButtonListItem * GetItemAt(int pos) const
QString GetChanNum(void) const
This is the channel "number", in the form 1, 1_2, 1-2, 1#1, etc.
Definition: programinfo.h:368
void storeClicked(void)
Definition: customedit.cpp:715
void RemoveItem(MythUIButtonListItem *item)
void BuildFocusList(void)
static Type kEventType
Definition: mythdialogbox.h:50
MythUIButtonList * m_clauseList
Definition: customedit.h:57
MythUIButton * m_storeButton
Definition: customedit.h:70
QDateTime GetScheduledStartTime(void) const
The scheduled start time of program.
Definition: programinfo.h:382
QVariant value(int i) const
Definition: mythdbcon.h:198
virtual void Close()
QString m_seSuffix
Definition: customedit.h:53
Holds information on recordings and videos.
Definition: programinfo.h:66
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
void SetMaxLength(const int length)
MythUIButton * m_recordButton
Definition: customedit.h:69
QSqlError lastError(void) const
Definition: mythdbcon.h:202
QString GetChannelSchedulingID(void) const
This is the unique programming identifier of a channel.
Definition: programinfo.h:375
void ruleChanged(MythUIButtonListItem *item)
Definition: customedit.cpp:574
QString GetDescription(void) const
Definition: programinfo.h:357
const MythUIButtonListItem * m_currentRuleItem
Definition: customedit.h:73
Construct a recording schedule.
void clauseChanged(MythUIButtonListItem *item)
Definition: customedit.cpp:601
QString evaluate(QString clause)
Definition: customedit.cpp:154
MythUIButtonList * m_ruleList
Definition: customedit.h:56
void scheduleCreated(int)
Definition: customedit.cpp:709
MythUITextEdit * m_subtitleEdit
Definition: customedit.h:65
QString GetSubtitle(void) const
Definition: programinfo.h:355
void loadData(void)
Definition: customedit.cpp:102
bool m_evaluate
Definition: customedit.h:51
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
Definition: mythuiutils.h:27
uint GetSeason(void) const
Definition: programinfo.h:358
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
Internal representation of a recording rule, mirrors the record table.
Definition: recordingrule.h:32
QDateTime GetScheduledEndTime(void) const
The scheduled end time of the program.
Definition: programinfo.h:389
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
void SetText(const QString &text, bool moveCursor=true)
MythUIType * GetFocusWidget(void) const
void storeRule(bool is_search, bool is_new)
Definition: customedit.cpp:828
MythMainWindow * GetMythMainWindow(void)
QString GetText(const QString &name="") const
QString GetSeriesID(void) const
Definition: programinfo.h:427
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:807
bool keyPressEvent(QKeyEvent *) override
Key event handler.
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QString m_exSuffix
Definition: customedit.h:54
uint GetChanID(void) const
This is the unique key used in the database to locate tuning information.
Definition: programinfo.h:364
void loadClauses(void)
Definition: customedit.cpp:220
uint GetRecordingRuleID(void) const
Definition: programinfo.h:441
QString GetProgramID(void) const
Definition: programinfo.h:428
MythUIText * m_clauseText
Definition: customedit.h:67
void deleteRule(void)
Definition: customedit.cpp:879
void textChanged(void)
Definition: customedit.cpp:590
MythUITextEdit * m_titleEdit
Definition: customedit.h:59
MythUITextEdit * m_descriptionEdit
Definition: customedit.h:62
int m_maxex
Definition: customedit.h:50
void customEvent(QEvent *event) override
Definition: customedit.cpp:899
void SetItemCurrent(MythUIButtonListItem *item)
QString executedQuery(void) const
Definition: mythdbcon.h:199
bool checkSyntax(void)
Definition: customedit.cpp:772
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
QString GetCategory(void) const
Definition: programinfo.h:361
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
Screen in which all other widgets are contained and rendered.
int GetCurrentPos() const
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:37
QString subtitle
Definition: customedit.h:79
bool keyPressEvent(QKeyEvent *) override
Key event handler.
Definition: customedit.cpp:923
QString title
Definition: customedit.h:78
QString GetText(void) const
MythUIButtonListItem * GetItemCurrent() const
uint GetEpisode(void) const
Definition: programinfo.h:359