6
\$\begingroup\$

Before I started, we had a few long introductory texts that were shown to users in different part of the editor window, depending on what type of object they are editing. The texts describe how to add a new item of that type, and gives hints how to handle. These texts may include different HTML tags like lists, strong, code blocks, external links, etc.

To make the intro easier to handle, and not hit new users with a wall of text, I am making this display collapsible. The basic idea is, only show very little text, some headlines marked as links, and if a user clicks one option, expand that section to show its contents. If they click another headline, only show that section, etc.

An intro box now simply starts by looking something like this:

Triggers react on game output.

  • How to add a new trigger now
  • How to add a new trigger from the command line
  • Where to find more information

Then if a user clicks say the first link in the box, it expands like:

Triggers react on game output.

  • How to add a new trigger now
  1. Click on the 'Add Item' icon above.
  2. Define a pattern that you want to trigger on.
  3. Select the appropriate pattern type.
  4. Define a clear text command that you want to send to the game if the trigger finds the pattern in the text from the game, or write a script for more complicated needs..
  5. Activate the trigger.
  • How to add a new trigger from the command line
  • Where to find more information

Also all texts are to be translated, by using Qt tr() mechanism. This includes comment lines starting with \\: in the line before, which explain the nature of the following string contents to translators. The translators like whole sentences, not only pieces puzzled together, so I work with lots arg() etc. Also they don't like too many HTML tags, so I started to extract them as well.

I don't want to have multiple copies of the same text in source, to prevent them drifting apart eventually. So I want to have a central place where all texts are defined once, then pull from it dynamically. Also, different objects have different number of sections, so again I needed to loop dynamically.

At best I would like these texts to live outside the code logic, but I did not accomplish that, yet. I arrived at a cascade of tr(), arg() and literal QStrings (encouraged and abbreviated as qsl() in this project to distinguish as deliberately no tr() necessary) - I may be wrong, but when I read )))}}}) it has some code smell.

How did I get here? Well, I made the following data structure:

struct introOption {
 QString name;
 QString headline;
 QString contents; 
};
struct introTextParts {
 QString summary;
 QVector<introOption> options;
};
QMap<EditorViewType, introTextParts> introAddItem;
void showIntro(const QString& = QString());

Now with showIntro() I show the basic intro, just headlines, no section expanded. If the user clicks the label, the clicked link will be given to showIntro(URL) and it will switch to different but similar text, with that clicked section expanded, its headline no longer a link but bold. Alas, if the clicked link was external, open it in a browser instead (like we did before automatically with RichText activated in the QLabel.)

void dlgTriggerEditor::slot_clickedMessageBox(const QString& URL)
{
 if (URL.startsWith("http")) {
 QDesktopServices::openUrl(URL);
 } else { // internal links used by expanding info text navigation
 showIntro(URL);
 }
}
void dlgTriggerEditor::showIntro(const QString& desiredOption)
{
 if (!introAddItem.contains(mCurrentView)) {
 qWarning() << "ERROR: dlgTriggerEditor::showIntro() undefined view";
 return;
 }
 introTextParts introAddCurrentItem = introAddItem.value(mCurrentView);
 QString introTextOptions;
 for (const auto &[name, headline, contents] : introAddCurrentItem.options) {
 introTextOptions.append(
 (name != desiredOption)
 ? qsl("<li><a href='%1'>%2</a></li>").arg(name, headline)
 : qsl("<li><strong>%1</strong>%2</li>").arg(headline, contents));
 }
 showInfo(qsl("<p>%1</p><ul>%2</ul>")
 .arg(introAddCurrentItem.summary, introTextOptions));
}

I am quite content with this solution so far. It works fine as expected.

Only doubt with the actual data which has come to this now:


 introAddItem.insert(EditorViewType::cmAliasView, {
 //: Headline for the Alias intro
 tr("Alias react on user input."), {
 //: Name of a selectable option for the Alias intro
 {qsl("alias1"), tr("How to add a new alias now"),
 //: Help contents of a selectable option for the Alias intro
 tr("<ol><li>Click on the 'Add Item' icon above.</li>"
 "<li>Define an input <strong>pattern</strong> either literally or with a Perl regular expression.</li>"
 "<li>Define a 'substitution' <strong>command</strong> to send to the game in clear text <strong>instead of the alias pattern</strong>, or write a script for more complicated needs.</li>"
 "<li><strong>Activate</strong> the alias.</li></ol>")},
 //: Name of a selectable option for the Alias intro
 {qsl("alias2"), tr("How to add a new alias from the input line"),
 qsl("%1%2%3%4").arg(
 //: Help contents of a selectable option for the Alias intro
 qsl("<p>%1</p>").arg(tr("There are a <a href='https://forums.mudlet.org/viewtopic.php?f=6&t=22609'>couple</a> of <a href='https://forums.mudlet.org/viewtopic.php?f=6&t=16462'>packages</a> that can help you.")),
 //: Part of the Alias intro - This introductory text will be followed by a Lua code example for a trigger.
 qsl("<p>%1</p>").arg(tr("Alias can also be defined from the input line in the main profile window like this:")),
 qsl("<p><code>%1</code></p>").arg(qsl("lua permAlias(&quot;%1&quot;, &quot;&quot;, &quot;%2&quot;, function() send(&quot;%3&quot;) echo(&quot;%4&quot;) end)").arg(
 //: Part of the Alias intro, code example for an alias - This is the name of the alias which reacts on the player typing "hi" by saying "Greetings, traveller!" in game.
 tr("My greetings"),
 //: Part of the Alias intro, code example for an alias - This is the text input from the player which will be reacted on by saying "Greetings, traveller!" in game.
 tr("hi"),
 //: Part of the Alias intro, code example for an alias - This is the command that Mudlet will send to the game after the player typed "hi".
 tr("say Greetings, traveller!"),
 //: Part of the Alias intro, code example for an alias - This is the confirmation text shown to the player after they typed "hi" and we said "Greetings, traveller!" in game.
 tr("We said hi!"))),
 //: Part of the Alias intro - This is the conclusion after the code example for an alias which reacts on the player typing "hi" by saying "Greetings, traveller!" in game.
 qsl("<p>%1</p>").arg("You can now greet by typing 'hi'"))},
 {qsl("alias3"), tr("Where to find more information"),
 qsl("<ul>%1%2%3%4</ul>").arg( // reduce clutter for translators
 qsl("<li><p>%1</p><li>").arg(tr("Watch a <a href='%1'>video demonstration</a> of the basic functionality.")
 .arg(qsl("https://youtu.be/Uz6EDvZYNvE"))),
 qsl("<li><p>%1</p></li>").arg(tr("Read the <a href='http://wiki.mudlet.org/w/Manual:Introduction#Aliases'>Introduction to Aliases</a> for a detailed overview.")),
 qsl("<li><p>%1</p>").arg(tr("Do you maybe have any other suggestions, questions or doubts?")),
 qsl("<p>%1</p></li>").arg(tr("Join our community on <a href='https://www.mudlet.org/chat'>Discord</a> or in <a href='https://forums.mudlet.org/'>Mudlet forums</a> - See you there!")))}}});
 introAddItem.insert(EditorViewType::cmTriggerView, {
 //: Headline for the Trigger intro
 tr("Triggers react on game output."), {
 //: Name of a selectable option for the Trigger intro
 {qsl("trigger1"), tr("How to add a new trigger now"),
 //: Help contents of a selectable option for the Trigger intro
 tr("<ol><li>Click on the 'Add Item' icon above.</li>"
 "<li>Define a <strong>pattern</strong> that you want to trigger on.</li>"
 "<li>Select the appropriate pattern <strong>type</strong>.</li>"
 "<li>Define a clear text <strong>command</strong> that you want to send to the game if the trigger finds the pattern in the text from the game, or write a script for more complicated needs..</li>"
 "<li><strong>Activate</strong> the trigger.</li></ol>")},
 //: Name of a selectable option for the Trigger intro
 {qsl("trigger2"), tr("How to add a new trigger from the input line"),
 qsl("%1%2%3%4").arg(
 //: Help contents of a selectable option for the Trigger intro
 qsl("<p>%1</p>").arg(tr("There are a <a href='https://forums.mudlet.org/viewtopic.php?f=6&t=22609'>couple</a> of <a href='https://forums.mudlet.org/viewtopic.php?f=6&t=16462'>packages</a> that can help you.")),
 //: Part of the Trigger intro - This introductory text will be followed by a Lua code example for a trigger.
 qsl("<p>%1</p>").arg(tr("Triggers can also be defined from the input line in the main profile window like this:")),
 qsl("<p><code>%1</code></p>").arg(qsl("lua permSubstringTrigger(&quot;%1&quot;, &quot;&quot;, &quot;%2&quot;, function() send(&quot;%3&quot;) end)").arg(
 //: Part of the Trigger intro, code example for a trigger - This is the name of the trigger which reacts on "You are thirsty" with "drink water".
 tr("My drink trigger"),
 //: Part of the Trigger intro, code example for a trigger - This is the text from game which will be triggered on, and reacted to with "drink water".
 tr("You are thirsty."),
 //: Part of the Trigger intro, code example for a trigger - This is the command sent to game after we triggered on text "You are thirsty." from game.
 tr("drink water"))),
 //: Part of the Trigger intro - This is the conclusion after the code example for a trigger which reacts on "You are thirsty" with "drink water".
 qsl("<p>%1</p>").arg("This will keep you refreshed."))},
 {qsl("trigger3"), tr("Where to find more information"),
 qsl("<ul>%1%2%3%4</ul>").arg( // reduce clutter for translators
 qsl("<li><p>%1</p><li>").arg(tr("Watch a <a href='%1'>video demonstration</a> of the basic functionality.")
 .arg(qsl("https://youtu.be/jYjop54-Y3I"))),
 qsl("<li><p>%1</p></li>").arg(tr("Read the <a href='http://wiki.mudlet.org/w/Manual:Introduction#Triggers'>Introduction to Triggers</a> for a detailed overview.")),
 qsl("<li><p>%1</p>").arg(tr("Do you maybe have any other suggestions, questions or doubts?")),
 qsl("<p>%1</p></li>").arg(tr("Join our community on <a href='https://www.mudlet.org/chat'>Discord</a> or in <a href='https://forums.mudlet.org/'>Mudlet forums</a> - See you there!")))}}});
 introAddItem.insert(EditorViewType::cmScriptView, {
 //: Headline for the Script intro
 tr("Scripts organize code and can react to events."), {
 //: Name of a selectable option for the Script intro
 {qsl("script1"), tr("How to add a new script now"),
 //: Help contents of a selectable option for the Script intro
 tr("<ol><li>Click on the 'Add Item' icon above.</li>"
 "<li>Enter a script in the box below. You can for example define <strong>functions</strong> to be called by other triggers, aliases, etc.</li>"
 "<li>If you write lua <strong>commands</strong> without defining a function, they will be run on Mudlet startup and each time you open the script for editing.</li>"
 "<li><strong>Activate</strong> the script.</li></ol>"
 "<p><strong>Note:</strong> Scripts are run automatically when viewed, even if they are deactivated.</p>")},
 //: Name of a selectable option for the Script intro
 {qsl("script2"), tr("How to have a script react to events"),
 //: Help contents of a selectable option for the Script intro
 tr("<p>You can register a list of <strong>events</strong> with the + and - symbols. If one of these events take place, the function with the same name as the script item itself will be called.</p>"
 "<p><strong>Note:</strong> Events can also be added to a script from the command line in the main profile window like this:</p>"
 "<p><code>lua registerAnonymousEventHandler(&quot;nameOfTheMudletEvent&quot;, &quot;nameOfYourFunctionToBeCalled&quot;)</code></p>")},
 {qsl("script3"), tr("Where to find more information"),
 qsl("<ul>%1%2%3%4</ul>").arg( // reduce clutter for translators
 qsl("<li><p>%1</p><li>").arg(tr("Watch a <a href='%1'>video demonstration</a> of the basic functionality.")
 .arg(qsl("https://youtu.be/10mJUh4Hq-A"))),
 qsl("<li><p>%1</p></li>").arg(tr("Read the <a href='http://wiki.mudlet.org/w/Manual:Introduction#Scripts'>Introduction to Scripts</a> for a detailed overview.")),
 qsl("<li><p>%1</p>").arg(tr("Do you maybe have any other suggestions, questions or doubts?")),
 qsl("<p>%1</p></li>").arg(tr("Join our community on <a href='https://www.mudlet.org/chat'>Discord</a> or in <a href='https://forums.mudlet.org/'>Mudlet forums</a> - See you there!")))}}});
 introAddItem.insert(EditorViewType::cmTimerView, {
 //: Headline for the Timer intro
 tr("Timers react after a timespan once or regularly."), {
 //: Name of a selectable option for the Timer intro
 {qsl("timer1"), tr("How to add a new timer now"),
 //: Help contents of a selectable option for the Timer intro
 tr("<ol><li>Click on the 'Add Item' icon above.</li>"
 "<li>Define the <strong>timespan</strong> after which the timer should react in a this format: hours : minutes : seconds.</li>"
 "<li>Define a clear text <strong>command</strong> that you want to send to the game when the time has passed, or write a script for more complicated needs.</li>"
 "<li><strong>Activate</strong> the timer.</li></ol>"
 "<p><strong>Note:</strong> If you want the trigger to react only once and not regularly, use the Lua tempTimer() function instead.</p>")},
 //: Name of a selectable option for the Timer intro
 {qsl("timer2"), tr("How to add a new timer from the input line"),
 //: Help contents of a selectable option for the Timer intro
 tr("<p>Timers can also be defined from the input line in the main profile window like this:</p>"
 "<p><code>lua tempTimer(3, function() echo(&quot;hello!\n&quot;) end)</code></p>"
 "<p>This will greet you exactly 3 seconds after it was made.</p>")},
 {qsl("timer3"), tr("Where to find more information"),
 qsl("<ul>%1%2%3</ul>").arg( // reduce clutter for translators
 qsl("<li><p>%1</p></li>").arg(tr("Read the <a href='http://wiki.mudlet.org/w/Manual:Introduction#Timers'>Introduction to Timers</a> for a detailed overview.")),
 qsl("<li><p>%1</p>").arg(tr("Do you maybe have any other suggestions, questions or doubts?")),
 qsl("<p>%1</p></li>").arg(tr("Join our community on <a href='https://www.mudlet.org/chat'>Discord</a> or in <a href='https://forums.mudlet.org/'>Mudlet forums</a> - See you there!")))}}});
 introAddItem.insert(EditorViewType::cmActionView, {
 //: Headline for the Button intro
 tr("Buttons react on mouse clicks."), {
 //: Name of a selectable option for the Button intro
 {qsl("button1"), tr("How to add a new button now"),
 //: Help contents of a selectable option for the Button intro
 tr("<ol><li>Add a new group to define a new <strong>button bar</strong> in case you don't have any.</li>"
 "<li>Add new groups as <strong>menus</strong> to a button bar or sub-menus to menus.<li>"
 "<li>Add new items as <strong>buttons</strong> to a button bar or menu or sub-menu.</li>"
 "<li>Define a clear text <strong>command</strong> that you want to send to the game if the button is pressed, or write a script for more complicated needs.</li>"
 "<li><strong>Activate</strong> the toolbar, menu or button. </li></ol>"
 "<p><strong>Note:</strong> Deactivated items will be hidden and if they are toolbars or menus then all the items they contain will be also be hidden.</p>"
 "<p><strong>Note:</strong> If a button is made a <strong>click-down</strong> button then you may also define a clear text command that you want to send to the game when the button is pressed a second time to uncheck it or to write a script to run when it happens - within such a script the Lua 'getButtonState()' function reports whether the button is up or down.</p>")},
// {qsl("button2"), tr("How to add a new button from the input line"),
// tr("")},
 {qsl("button3"), tr("Where to find more information"),
 qsl("<ul>%1%2%3</ul>").arg( // reduce clutter for translators
 qsl("<li><p>%1</p></li>").arg(tr("Read the <a href='http://wiki.mudlet.org/w/Manual:Introduction#Buttons'>Introduction to Buttons</a> for a detailed overview.")),
 qsl("<li><p>%1</p>").arg(tr("Do you maybe have any other suggestions, questions or doubts?")),
 qsl("<p>%1</p></li>").arg(tr("Join our community on <a href='https://www.mudlet.org/chat'>Discord</a> or in <a href='https://forums.mudlet.org/'>Mudlet forums</a> - See you there!")))}}});
 introAddItem.insert(EditorViewType::cmKeysView, {
 //: Headline for the Keys intro
 tr("Keys react on keyboard presses."), {
 //: Name of a selectable option for the Keys intro
 {qsl("key1"), tr("How to add a new keybinding now"),
 //: Help contents of a selectable option for the Keys intro
 tr("<ol><li>Click on the 'Add Item' icon above.</li>"
 "<li>Click on <strong>'grab key'</strong> and then press your key combination, e.g. including modifier keys like Control, Shift, etc.</li>"
 "<li>Define a clear text <strong>command</strong> that you want to send to the game if the button is pressed, or write a script for more complicated needs.</li>"
 "<li><strong>Activate</strong> the new key binding.</li></ol>")},
 //: Name of a selectable option for the Keys intro
 {qsl("key2"), tr("How to add a new keybinding from the input line"),
 //: Help contents of a selectable option for the Keys intro
 tr("<p>Keys can be defined from the input line in the main profile window like this:</p>"
 "<p><code>lua permKey(&quot;my jump key&quot;, &quot;&quot;, mudlet.key.F8, [[send(&quot;jump&quot;]]) end)</code></p>"
 "<p>Pressing F8 will make you jump.</p>")},
 {qsl("key3"), tr("Where to find more information"),
 qsl("<ul>%1%2%3%4</ul>").arg( // reduce clutter for translators
 qsl("<li><p>%1</p><li>").arg(tr("Watch a <a href='%1'>video demonstration</a> of the basic functionality.")
 .arg(qsl("https://youtu.be/ZYRPZ-8fJWA"))),
 qsl("<li><p>%1</p></li>").arg(tr("Read the <a href='http://wiki.mudlet.org/w/Manual:Introduction#Keybindings'>Introduction to Keybindings</a> for a detailed overview.")),
 qsl("<li><p>%1</p>").arg(tr("Do you maybe have any other suggestions, questions or doubts?")),
 qsl("<p>%1</p></li>").arg(tr("Join our community on <a href='https://www.mudlet.org/chat'>Discord</a> or in <a href='https://forums.mudlet.org/'>Mudlet forums</a> - See you there!")))}}});
 introAddItem.insert(EditorViewType::cmVarsView, {
 //: Headline for the Variable intro
 tr("Variables store information."), {
 //: Name of a selectable option for the Variable intro
 {qsl("variable1"), tr("How to add a new variable now"),
 //: Help contents of a selectable option for the Variable intro
 tr("<ol><li>Click on the 'Add Item' icon above. To add a table instead click 'Add Group'.</li>"
 "<li>Select type of variable value (can be a string, integer, boolean)</li>"
 "<li>Enter the value you want to store in this variable.</li>"
 "<li>If you want to keep the variable in your next Mudlet sessions, check the checkbox in the list of variables to the left.</li>"
 "<li>To remove a variable manually, set it to 'nil' or click on the 'Delete' icon above.</li></ol>"
 "<p><strong>Note:</strong> Variables created here won't be saved when Mudlet shuts down unless you check their checkbox in the list of variables to the left. You could also create scripts with the variables instead.</p>")},
 //: Name of a selectable option for the Variable intro
 {qsl("variable2"), tr("How to add a new variable from the input line"),
 //: Help contents of a selectable option for the Variable intro
 tr("<p>Variables and tables can also be defined from the input line in the main profile window like this:</p>"
 "<p><code>lua foo = &quot;bar&quot;</code></p>"
 "<p>This will create a string called 'foo' with 'bar' as its value.</p>")},
 {qsl("variable3"), tr("Where to find more information"),
 qsl("<ul>%1%2%3</ul>").arg( // reduce clutter for translators
 qsl("<li><p>%1</p></li>").arg(tr("Read the <a href='http://wiki.mudlet.org/w/Manual:Introduction#Variables'>Introduction to Variables</a> for a detailed overview.")),
 qsl("<li><p>%1</p>").arg(tr("Do you maybe have any other suggestions, questions or doubts?")),
 qsl("<p>%1</p></li>").arg(tr("Join our community on <a href='https://www.mudlet.org/chat'>Discord</a> or in <a href='https://forums.mudlet.org/'>Mudlet forums</a> - See you there!")))}}});

I did not finish refactoring all the data yet, but you get the gist. Am I heading down the right path here, or how would you recommend differently maybe?

toolic
14.3k5 gold badges29 silver badges200 bronze badges
asked Aug 22 at 23:33
\$\endgroup\$

1 Answer 1

7
\$\begingroup\$

Create collapsibles in HTML/CSS/JS

At best I would like these texts to live outside the code logic, but I did not accomplish that, yet.

I would strongly recommend you try to achieve this goal first. At first glance, the current code looks like an unmaintainable mess. There might be ways to make it look a bit better, but indeed the proper solution is to store the introductions in external files.

You could perhaps write some code to find the individual sections, split them, and add links. However, consider that a QWebView is pretty advanced and can handle CSS and JavaScript as well. So I would try to have QWebView handle the collapsing. You can do this in pure HTML+CSS, or you could use some JavaScript to create a collapsible.

Addressing some of the issues that were raised:

Safety

It's indeed not great if a translator could add arbitrary JavaScript. If you cannot trust the translators, you have to sanitize the translations first. That might then be a good idea even for your current approach. What if the translator adds <img> or <a> URLs that point to things you didn't expect? What if they translate:

Watch a <a href='%1'>video demonstration</a>

With:

Bekijk een <a ignore='%1' href='https://scam.example.com/'>demonstratiefilm</a>

You might also consider storing the texts that are to be translated in Markdown format, and convert it to HTML on the fly. Of course, make sure you use a library that doesn't pass raw HTML in the Markdown to the output.

Comments for translators

Comments for translators can still be put in external text files. For example, you can add them as HTML comments:

<h1>Alias react on user input</h1>
<!-- Name of a selectable option for the Alias intro -->
<details id="alias1">
 <summary>How to add a new alias now</summary>
 <!-- Help contents of a selectable option for the Alias intro -->
 <ol><li>Click on the 'Add Item' icon above.</li>
 ...</ol>
</details>
<details id="alias2">
 <summary>How to add a new alias from the input line</summary>
 <!-- Help contents of a selectable option for the Alias intro -->
 There are a <a href='https://forums.mudlet.org/viewtopic.php?f=6&t=22609'>couple</a> of <a href='https://forums.mudlet.org/viewtopic.php?f=6&t=16462'>packages</a> that can help you.
 ...
</details>
...

Try it live.

When you can't use a QWebView

If you are restricted to using QLabels, you could still store the texts in XHTML format, then use Qt's HTML capabilities to parse it and split it into individual sections, and render those as QLabels. At first glance that seems much more complicated, but the advantage is that you only have to write that code once.

Use collapsible Qt widgets

Instead of implementing your own collapsibles using URLs and triggers, there might be ways to make Qt widgets themselves truly collapsible. See for example this StackOverflow question.

Consider creating a table of contents instead

By making the sections collapsible, you basically remove the wall of text, and replace it with a list of summaries, with the option to click on one to get the full description. You can get effectively the same effect by just adding a table of contents on top of the full text. Consider:

<h1>Alias react on user input</h1>
<ol><li><a href="#alias1">How to add a new alias now</a></li>
 <li><a href="#alias2">How to add a new alias from the input line</a></li>
 ...</ol>
<h2 id="alias1">How to add a new alias now</h2>
 <!-- Help contents of a selectable option for the Alias intro -->
 <ol><li>Click on the 'Add Item' icon above.</li>
 ...</ol>
<h2 id="alias2">How to add a new alias from the input line</h2>
 <!-- Help contents of a selectable option for the Alias intro -->
 There are a <a href='https://forums.mudlet.org/viewtopic.php?f=6&t=22609'>couple</a> of <a href='https://forums.mudlet.org/viewtopic.php?f=6&t=16462'>packages</a> that can help you.
 ...

This should be supported by QLabel. You might also want to put it in a QScrollArea then. Try it live.

answered Aug 23 at 7:45
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Not sure how to best approach this solution. For example, how to add the "comments for translators" to an external file then? In Qt docs: Writing Source Code for Translation I only find these //: lines recommended, and I am not sure how they could be handled dynamically from external sources. What is more, a QWebView seems much more potent than a mere QLabel. Not sure the risk in giving this tool to translators to fill. They could put arbitrary JavaScript, I reckon? \$\endgroup\$ Commented Aug 23 at 8:37
  • \$\begingroup\$ Looking at doc.qt.io/qt-6/qtwebview-index.html#limitations (Qt docs: WebView limitations) their events may not "propagate into the Qt event delivery system" - Does that mean, I can't open external links in the system's browser from there? Then it's a no go. \$\endgroup\$ Commented Aug 23 at 8:45

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.