2 * Copyright © 2021 Damyan Ivanov.
3 * This file is part of MoLe.
4 * MoLe is free software: you can distribute it and/or modify it
5 * under the term of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your opinion), any later version.
9 * MoLe is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License terms for details.
14 * You should have received a copy of the GNU General Public License
15 * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
18 package net.ktnx.mobileledger.ui.templates;
20 import android.text.Editable;
21 import android.text.TextWatcher;
22 import android.view.LayoutInflater;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.widget.TextView;
27 import androidx.annotation.NonNull;
28 import androidx.appcompat.app.AppCompatActivity;
29 import androidx.recyclerview.widget.AsyncListDiffer;
30 import androidx.recyclerview.widget.DiffUtil;
31 import androidx.recyclerview.widget.RecyclerView;
33 import net.ktnx.mobileledger.R;
34 import net.ktnx.mobileledger.databinding.TemplateDetailsAccountBinding;
35 import net.ktnx.mobileledger.databinding.TemplateDetailsHeaderBinding;
36 import net.ktnx.mobileledger.db.AccountAutocompleteAdapter;
37 import net.ktnx.mobileledger.db.TemplateBase;
38 import net.ktnx.mobileledger.model.Data;
39 import net.ktnx.mobileledger.model.TemplateDetailsItem;
40 import net.ktnx.mobileledger.ui.QRScanCapableFragment;
41 import net.ktnx.mobileledger.ui.TemplateDetailSourceSelectorFragment;
42 import net.ktnx.mobileledger.utils.Logger;
43 import net.ktnx.mobileledger.utils.Misc;
45 import org.jetbrains.annotations.NotNull;
47 import java.text.ParseException;
48 import java.util.ArrayList;
49 import java.util.List;
50 import java.util.Locale;
51 import java.util.regex.Matcher;
52 import java.util.regex.Pattern;
54 class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter.ViewHolder> {
55 private static final String D_TEMPLATE_UI = "template-ui";
56 private final AsyncListDiffer<TemplateDetailsItem> differ;
57 public TemplateDetailsAdapter() {
59 setHasStableIds(true);
60 differ = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<TemplateDetailsItem>() {
62 public boolean areItemsTheSame(@NonNull TemplateDetailsItem oldItem,
63 @NonNull TemplateDetailsItem newItem) {
64 if (oldItem.getType() != newItem.getType())
67 .equals(TemplateDetailsItem.Type.HEADER))
68 return true; // only one header item, ever
69 // the rest is comparing two account row items
70 return oldItem.asAccountRowItem()
71 .getId() == newItem.asAccountRowItem()
75 public boolean areContentsTheSame(@NonNull TemplateDetailsItem oldItem,
76 @NonNull TemplateDetailsItem newItem) {
78 .equals(TemplateDetailsItem.Type.HEADER))
80 TemplateDetailsItem.Header oldHeader = oldItem.asHeaderItem();
81 TemplateDetailsItem.Header newHeader = newItem.asHeaderItem();
83 return oldHeader.equalContents(newHeader);
86 TemplateDetailsItem.AccountRow oldAcc = oldItem.asAccountRowItem();
87 TemplateDetailsItem.AccountRow newAcc = newItem.asAccountRowItem();
89 return oldAcc.equalContents(newAcc);
95 public long getItemId(int position) {
96 // header item is always first and IDs id may duplicate some of the account IDs
99 TemplateDetailsItem.AccountRow accRow = differ.getCurrentList()
102 return accRow.getId();
105 public int getItemViewType(int position) {
107 return differ.getCurrentList()
114 public TemplateDetailsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
116 final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
118 case TemplateDetailsItem.TYPE.header:
119 return new Header(TemplateDetailsHeaderBinding.inflate(inflater, parent, false));
120 case TemplateDetailsItem.TYPE.accountItem:
121 return new AccountRow(
122 TemplateDetailsAccountBinding.inflate(inflater, parent, false));
124 throw new IllegalStateException("Unsupported view type " + viewType);
128 public void onBindViewHolder(@NonNull TemplateDetailsAdapter.ViewHolder holder, int position) {
129 TemplateDetailsItem item = differ.getCurrentList()
134 public int getItemCount() {
135 return differ.getCurrentList()
138 public void setTemplateItems(List<TemplateBase> items) {
139 ArrayList<TemplateDetailsItem> list = new ArrayList<>();
140 for (TemplateBase p : items) {
141 TemplateDetailsItem item = TemplateDetailsItem.fromRoomObject(p);
146 public void setItems(List<TemplateDetailsItem> items) {
147 differ.submitList(items);
149 public String getMatchGroupText(int groupNumber) {
150 TemplateDetailsItem.Header header = getHeader();
151 Pattern p = header.getCompiledPattern();
155 final String testText = Misc.nullIsEmpty(header.getTestText());
156 Matcher m = p.matcher(testText);
157 if (m.matches() && m.groupCount() >= groupNumber)
158 return m.group(groupNumber);
162 protected TemplateDetailsItem.Header getHeader() {
163 return differ.getCurrentList()
168 private enum HeaderDetail {DESCRIPTION, COMMENT, DATE_YEAR, DATE_MONTH, DATE_DAY}
170 private enum AccDetail {ACCOUNT, COMMENT, AMOUNT}
172 public abstract static class ViewHolder extends RecyclerView.ViewHolder {
173 ViewHolder(@NonNull View itemView) {
176 abstract void bind(TemplateDetailsItem item);
179 public class Header extends ViewHolder {
180 private final TemplateDetailsHeaderBinding b;
181 public Header(@NonNull TemplateDetailsHeaderBinding binding) {
182 super(binding.getRoot());
185 TextWatcher templateNameWatcher = new TextWatcher() {
187 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
189 public void onTextChanged(CharSequence s, int start, int before, int count) {}
191 public void afterTextChanged(Editable s) {
192 final TemplateDetailsItem.Header header = getItem();
193 Logger.debug(D_TEMPLATE_UI,
194 "Storing changed template name " + s + "; header=" + header);
195 header.setName(String.valueOf(s));
198 b.templateName.addTextChangedListener(templateNameWatcher);
200 TextWatcher patternWatcher = new TextWatcher() {
202 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
204 public void onTextChanged(CharSequence s, int start, int before, int count) {}
206 public void afterTextChanged(Editable s) {
207 final TemplateDetailsItem.Header header = getItem();
208 Logger.debug(D_TEMPLATE_UI,
209 "Storing changed pattern " + s + "; header=" + header);
210 header.setPattern(String.valueOf(s));
212 checkPatternError(header);
215 b.pattern.addTextChangedListener(patternWatcher);
217 TextWatcher testTextWatcher = new TextWatcher() {
219 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
221 public void onTextChanged(CharSequence s, int start, int before, int count) {}
223 public void afterTextChanged(Editable s) {
224 final TemplateDetailsItem.Header header = getItem();
225 Logger.debug(D_TEMPLATE_UI,
226 "Storing changed test text " + s + "; header=" + header);
227 header.setTestText(String.valueOf(s));
229 checkPatternError(header);
232 b.testText.addTextChangedListener(testTextWatcher);
234 TextWatcher transactionDescriptionWatcher = new TextWatcher() {
236 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
239 public void onTextChanged(CharSequence s, int start, int before, int count) {
243 public void afterTextChanged(Editable s) {
244 final TemplateDetailsItem.Header header = getItem();
245 Logger.debug(D_TEMPLATE_UI,
246 "Storing changed transaction description " + s + "; header=" + header);
247 header.setTransactionDescription(String.valueOf(s));
250 b.transactionDescription.addTextChangedListener(transactionDescriptionWatcher);
251 TextWatcher transactionCommentWatcher = new TextWatcher() {
253 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
257 public void onTextChanged(CharSequence s, int start, int before, int count) {
261 public void afterTextChanged(Editable s) {
262 final TemplateDetailsItem.Header header = getItem();
263 Logger.debug(D_TEMPLATE_UI,
264 "Storing changed transaction description " + s + "; header=" + header);
265 header.setTransactionComment(String.valueOf(s));
268 b.transactionComment.addTextChangedListener(transactionCommentWatcher);
271 private TemplateDetailsItem.Header getItem() {
272 int pos = getAdapterPosition();
273 return differ.getCurrentList()
277 private void selectHeaderDetailSource(View v, HeaderDetail detail) {
278 TemplateDetailsItem.Header header = getItem();
279 Logger.debug(D_TEMPLATE_UI, "header is " + header);
280 TemplateDetailSourceSelectorFragment sel =
281 TemplateDetailSourceSelectorFragment.newInstance(1, header.getPattern(),
282 header.getTestText());
283 sel.setOnSourceSelectedListener((literal, group) -> {
287 header.switchToLiteralTransactionDescription();
290 header.switchToLiteralTransactionComment();
293 header.switchToLiteralDateYear();
296 header.switchToLiteralDateMonth();
299 header.switchToLiteralDateDay();
302 throw new IllegalStateException("Unexpected detail " + detail);
308 header.setTransactionDescriptionMatchGroup(group);
311 header.setTransactionCommentMatchGroup(group);
314 header.setDateYearMatchGroup(group);
317 header.setDateMonthMatchGroup(group);
320 header.setDateDayMatchGroup(group);
323 throw new IllegalStateException("Unexpected detail " + detail);
327 notifyItemChanged(getAdapterPosition());
329 final AppCompatActivity activity = (AppCompatActivity) v.getContext();
330 sel.show(activity.getSupportFragmentManager(), "template-details-source-selector");
333 void bind(TemplateDetailsItem item) {
334 TemplateDetailsItem.Header header = item.asHeaderItem();
335 Logger.debug(D_TEMPLATE_UI, "Binding to header " + header);
337 String groupNoText = b.getRoot()
339 .getString(R.string.template_item_match_group_source);
341 b.templateName.setText(header.getName());
342 b.pattern.setText(header.getPattern());
343 b.testText.setText(header.getTestText());
345 if (header.hasLiteralDateYear()) {
346 b.templateDetailsYearSource.setText(R.string.template_details_source_literal);
347 final Integer dateYear = header.getDateYear();
348 b.templateDetailsDateYear.setText(
349 (dateYear == null) ? null : String.valueOf(dateYear));
350 b.templateDetailsDateYearLayout.setVisibility(View.VISIBLE);
353 b.templateDetailsDateYearLayout.setVisibility(View.GONE);
354 b.templateDetailsYearSource.setText(
355 String.format(Locale.US, groupNoText, header.getDateYearMatchGroup(),
356 getMatchGroupText(header.getDateYearMatchGroup())));
358 b.templateDetailsYearSourceLabel.setOnClickListener(
359 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
360 b.templateDetailsYearSource.setOnClickListener(
361 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
363 if (header.hasLiteralDateMonth()) {
364 b.templateDetailsMonthSource.setText(R.string.template_details_source_literal);
365 final Integer dateMonth = header.getDateMonth();
366 b.templateDetailsDateMonth.setText(
367 (dateMonth == null) ? null : String.valueOf(dateMonth));
368 b.templateDetailsDateMonthLayout.setVisibility(View.VISIBLE);
371 b.templateDetailsDateMonthLayout.setVisibility(View.GONE);
372 b.templateDetailsMonthSource.setText(
373 String.format(Locale.US, groupNoText, header.getDateMonthMatchGroup(),
374 getMatchGroupText(header.getDateMonthMatchGroup())));
376 b.templateDetailsMonthSourceLabel.setOnClickListener(
377 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
378 b.templateDetailsMonthSource.setOnClickListener(
379 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
381 if (header.hasLiteralDateDay()) {
382 b.templateDetailsDaySource.setText(R.string.template_details_source_literal);
383 final Integer dateDay = header.getDateDay();
384 b.templateDetailsDateDay.setText(
385 (dateDay == null) ? null : String.valueOf(dateDay));
386 b.templateDetailsDateDayLayout.setVisibility(View.VISIBLE);
389 b.templateDetailsDateDayLayout.setVisibility(View.GONE);
390 b.templateDetailsDaySource.setText(
391 String.format(Locale.US, groupNoText, header.getDateDayMatchGroup(),
392 getMatchGroupText(header.getDateDayMatchGroup())));
394 b.templateDetailsDaySourceLabel.setOnClickListener(
395 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
396 b.templateDetailsDaySource.setOnClickListener(
397 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
399 if (header.hasLiteralTransactionDescription()) {
400 b.templateTransactionDescriptionSource.setText(
401 R.string.template_details_source_literal);
402 b.transactionDescription.setText(header.getTransactionDescription());
403 b.transactionDescriptionLayout.setVisibility(View.VISIBLE);
406 b.transactionDescriptionLayout.setVisibility(View.GONE);
407 b.templateTransactionDescriptionSource.setText(String.format(Locale.US, groupNoText,
408 header.getTransactionDescriptionMatchGroup(),
409 getMatchGroupText(header.getTransactionDescriptionMatchGroup())));
412 b.templateTransactionDescriptionSourceLabel.setOnClickListener(
413 v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
414 b.templateTransactionDescriptionSource.setOnClickListener(
415 v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
417 if (header.hasLiteralTransactionComment()) {
418 b.templateTransactionCommentSource.setText(
419 R.string.template_details_source_literal);
420 b.transactionComment.setText(header.getTransactionComment());
421 b.transactionCommentLayout.setVisibility(View.VISIBLE);
424 b.transactionCommentLayout.setVisibility(View.GONE);
425 b.templateTransactionCommentSource.setText(String.format(Locale.US, groupNoText,
426 header.getTransactionCommentMatchGroup(),
427 getMatchGroupText(header.getTransactionCommentMatchGroup())));
430 b.templateTransactionCommentSourceLabel.setOnClickListener(
431 v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
432 b.templateTransactionCommentSource.setOnClickListener(
433 v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
435 b.templateDetailsHeadScanQrButton.setOnClickListener(this::scanTestQR);
437 checkPatternError(header);
439 private void checkPatternError(TemplateDetailsItem.Header item) {
440 if (item.getPatternError() != null) {
441 b.patternLayout.setError(item.getPatternError());
442 b.patternHintTitle.setVisibility(View.GONE);
443 b.patternHintText.setVisibility(View.GONE);
446 b.patternLayout.setError(null);
447 if (item.testMatch() != null) {
448 b.patternHintText.setText(item.testMatch());
449 b.patternHintTitle.setVisibility(View.VISIBLE);
450 b.patternHintText.setVisibility(View.VISIBLE);
453 b.patternLayout.setError(null);
454 b.patternHintTitle.setVisibility(View.GONE);
455 b.patternHintText.setVisibility(View.GONE);
460 private void scanTestQR(View view) {
461 QRScanCapableFragment.triggerQRScan();
465 public class AccountRow extends ViewHolder {
466 private final TemplateDetailsAccountBinding b;
467 public AccountRow(@NonNull TemplateDetailsAccountBinding binding) {
468 super(binding.getRoot());
471 TextWatcher accountNameWatcher = new TextWatcher() {
473 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
475 public void onTextChanged(CharSequence s, int start, int before, int count) {}
477 public void afterTextChanged(Editable s) {
478 TemplateDetailsItem.AccountRow accRow = getItem();
479 Logger.debug(D_TEMPLATE_UI,
480 "Storing changed account name " + s + "; accRow=" + accRow);
481 accRow.setAccountName(String.valueOf(s));
484 b.templateDetailsAccountName.addTextChangedListener(accountNameWatcher);
485 b.templateDetailsAccountName.setAdapter(new AccountAutocompleteAdapter(b.getRoot()
487 b.templateDetailsAccountName.setOnItemClickListener(
488 (parent, view, position, id) -> b.templateDetailsAccountName.setText(
489 ((TextView) view).getText()));
490 TextWatcher accountCommentWatcher = new TextWatcher() {
492 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
494 public void onTextChanged(CharSequence s, int start, int before, int count) {}
496 public void afterTextChanged(Editable s) {
497 TemplateDetailsItem.AccountRow accRow = getItem();
498 Logger.debug(D_TEMPLATE_UI,
499 "Storing changed account comment " + s + "; accRow=" + accRow);
500 accRow.setAccountComment(String.valueOf(s));
503 b.templateDetailsAccountComment.addTextChangedListener(accountCommentWatcher);
505 b.templateDetailsAccountAmount.addTextChangedListener(new TextWatcher() {
507 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
511 public void onTextChanged(CharSequence s, int start, int before, int count) {
515 public void afterTextChanged(Editable s) {
516 TemplateDetailsItem.AccountRow accRow = getItem();
518 String str = String.valueOf(s);
519 if (Misc.emptyIsNull(str) == null) {
520 accRow.setAmount(null);
524 final float amount = Data.parseNumber(str);
525 accRow.setAmount(amount);
526 b.templateDetailsAccountAmountLayout.setError(null);
528 Logger.debug(D_TEMPLATE_UI, String.format(Locale.US,
529 "Storing changed account amount %s [%4.2f]; accRow=%s", s,
532 catch (NumberFormatException | ParseException e) {
533 b.templateDetailsAccountAmountLayout.setError("!");
538 b.templateDetailsAccountAmount.setOnFocusChangeListener((v, hasFocus) -> {
542 TemplateDetailsItem.AccountRow accRow = getItem();
543 if (!accRow.hasLiteralAmount())
545 Float amt = accRow.getAmount();
549 b.templateDetailsAccountAmount.setText(Data.formatNumber(amt));
552 b.negateAmountSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
553 getItem().setNegateAmount(isChecked);
554 b.templateDetailsNegateAmountText.setText(
555 isChecked ? R.string.template_account_change_amount_sign
556 : R.string.template_account_keep_amount_sign);
558 final View.OnClickListener negLabelClickListener = (view) -> {
559 b.negateAmountSwitch.toggle();
561 b.templateDetailsNegateAmountLabel.setOnClickListener(negLabelClickListener);
562 b.templateDetailsNegateAmountText.setOnClickListener(negLabelClickListener);
565 void bind(TemplateDetailsItem item) {
566 String groupNoText = b.getRoot()
568 .getString(R.string.template_item_match_group_source);
570 TemplateDetailsItem.AccountRow accRow = item.asAccountRowItem();
571 if (accRow.hasLiteralAccountName()) {
572 b.templateDetailsAccountNameLayout.setVisibility(View.VISIBLE);
573 b.templateDetailsAccountName.setText(accRow.getAccountName());
574 b.templateDetailsAccountNameSource.setText(
575 R.string.template_details_source_literal);
578 b.templateDetailsAccountNameLayout.setVisibility(View.GONE);
579 b.templateDetailsAccountNameSource.setText(
580 String.format(Locale.US, groupNoText, accRow.getAccountNameMatchGroup(),
581 getMatchGroupText(accRow.getAccountNameMatchGroup())));
584 if (accRow.hasLiteralAccountComment()) {
585 b.templateDetailsAccountCommentLayout.setVisibility(View.VISIBLE);
586 b.templateDetailsAccountComment.setText(accRow.getAccountComment());
587 b.templateDetailsAccountCommentSource.setText(
588 R.string.template_details_source_literal);
591 b.templateDetailsAccountCommentLayout.setVisibility(View.GONE);
592 b.templateDetailsAccountCommentSource.setText(
593 String.format(Locale.US, groupNoText, accRow.getAccountCommentMatchGroup(),
594 getMatchGroupText(accRow.getAccountCommentMatchGroup())));
597 if (accRow.hasLiteralAmount()) {
598 b.templateDetailsAccountAmountSource.setText(
599 R.string.template_details_source_literal);
600 b.templateDetailsAccountAmount.setVisibility(View.VISIBLE);
601 Float amt = accRow.getAmount();
602 b.templateDetailsAccountAmount.setText((amt == null) ? null : String.format(
603 Data.locale.getValue(), "%,4.2f", (accRow.getAmount())));
604 b.negateAmountSwitch.setVisibility(View.GONE);
605 b.templateDetailsNegateAmountLabel.setVisibility(View.GONE);
606 b.templateDetailsNegateAmountText.setVisibility(View.GONE);
609 b.templateDetailsAccountAmountSource.setText(
610 String.format(Locale.US, groupNoText, accRow.getAmountMatchGroup(),
611 getMatchGroupText(accRow.getAmountMatchGroup())));
612 b.templateDetailsAccountAmountLayout.setVisibility(View.GONE);
613 b.negateAmountSwitch.setVisibility(View.VISIBLE);
614 b.negateAmountSwitch.setChecked(accRow.isNegateAmount());
615 b.templateDetailsNegateAmountText.setText(
616 accRow.isNegateAmount() ? R.string.template_account_change_amount_sign
617 : R.string.template_account_keep_amount_sign);
618 b.templateDetailsNegateAmountLabel.setVisibility(View.VISIBLE);
619 b.templateDetailsNegateAmountText.setVisibility(View.VISIBLE);
622 b.templateAccountNameSourceLabel.setOnClickListener(
623 v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
624 b.templateDetailsAccountNameSource.setOnClickListener(
625 v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
626 b.templateAccountCommentSourceLabel.setOnClickListener(
627 v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
628 b.templateDetailsAccountCommentSource.setOnClickListener(
629 v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
630 b.templateAccountAmountSourceLabel.setOnClickListener(
631 v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
632 b.templateDetailsAccountAmountSource.setOnClickListener(
633 v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
635 private @NotNull TemplateDetailsItem.AccountRow getItem() {
636 return differ.getCurrentList()
637 .get(getAdapterPosition())
640 private void selectAccountRowDetailSource(View v, AccDetail detail) {
641 TemplateDetailsItem.AccountRow accRow = getItem();
642 final TemplateDetailsItem.Header header = getHeader();
643 Logger.debug(D_TEMPLATE_UI, "header is " + header);
644 TemplateDetailSourceSelectorFragment sel =
645 TemplateDetailSourceSelectorFragment.newInstance(1, header.getPattern(),
646 header.getTestText());
647 sel.setOnSourceSelectedListener((literal, group) -> {
651 accRow.switchToLiteralAccountName();
654 accRow.switchToLiteralAccountComment();
657 accRow.switchToLiteralAmount();
660 throw new IllegalStateException("Unexpected detail " + detail);
666 accRow.setAccountNameMatchGroup(group);
669 accRow.setAccountCommentMatchGroup(group);
672 accRow.setAmountMatchGroup(group);
675 throw new IllegalStateException("Unexpected detail " + detail);
679 notifyItemChanged(getAdapterPosition());
681 final AppCompatActivity activity = (AppCompatActivity) v.getContext();
682 sel.show(activity.getSupportFragmentManager(), "template-details-source-selector");