mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-13 18:19:33 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8eafe27955 | |||
| 2af95456a2 | |||
| 8ce48dbcf6 | |||
| 92072b4225 | |||
| 4e823dd3a3 | |||
| 53d9cecdaf | |||
| 680f1f49ff | |||
| 6574272ca8 | |||
| f5c4aa2be2 | |||
| 2498861003 | |||
| 25923e82a4 | |||
| 61cb57e48d | |||
| 4985d836ea | |||
| e1a79a0ff1 | |||
| d72d935607 | |||
| 4ed06b9eff | |||
| fa4623db7c | |||
| a44e8b9921 | |||
| 6b4f1a552e | |||
| 3d32682a67 |
@@ -1,82 +1,55 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
from urllib.error import HTTPError
|
||||
|
||||
main_repo_url = "https://github.com/eried/portapack-mayhem.git"
|
||||
main_repo_identifier = "eried/portapack-mayhem"
|
||||
# ^^&^ change these if main repo changed
|
||||
import requests
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
os.system('gh repo set-default ' + main_repo_url)
|
||||
|
||||
raw_git = os.popen('git log next --since="1200 hours" --pretty=format:"- ^%h^ - {USERNAME}*_%al_%an*: %s"').read()
|
||||
# Set up your personal access token and the repository details
|
||||
token = os.environ.get('GH_TOKEN')
|
||||
repo_owner = "eried"
|
||||
repo_name = "portapack-mayhem"
|
||||
|
||||
|
||||
# ^ as github's rule, a real username can contains "-" but not "_" and "*", so use these two to seperate things.
|
||||
# ^ during test at 2023-07-10, %al could return 3 different types of values:
|
||||
# : 123+abc <<<< abc is real username
|
||||
# : def <<<< def is 3rd level domain of unhidden email, drop this, fetch info from github cli client
|
||||
# : note that github will probably change in the future
|
||||
def print_stable_changelog(previous_sha):
|
||||
url = f"compare/{previous_sha}...next"
|
||||
commits = handle_get_request(url)
|
||||
for commit in commits["commits"]:
|
||||
# Print the commit details
|
||||
print(format_output(commit))
|
||||
|
||||
|
||||
def compute_username(line):
|
||||
line_org = line
|
||||
stripped = re.search(r'(?<=\*)(.*?)(?=\*)', line).group(0) # get the string between "*" and "*"
|
||||
now_item_hash = re.search(r'(?<=\^)(.*?)(?=\^)', line).group(0) # get the string between "*" and "*"
|
||||
stripped_org = stripped
|
||||
stripped = re.search(r'(?<=\_)(.*?)(?=\_)', stripped).group(0) # get the string between "_" and "_"
|
||||
stripped_fallback = stripped
|
||||
# now it's like "123+abc" or "def"
|
||||
|
||||
if ("+" in stripped): # 123+abc
|
||||
stripped = re.sub(r'^.*?\+', "", stripped)
|
||||
elif not ("+" in stripped): # maybe not real username, dropped, fetch from github cli client
|
||||
fetched_now_item_json = os.popen(
|
||||
'gh search commits repo:' + main_repo_identifier + ' --json author --hash ' + now_item_hash).read()
|
||||
stripped = extract_first_login(fetched_now_item_json)
|
||||
if stripped is False: # 403
|
||||
return "@" + stripped_fallback
|
||||
elif stripped == "app/": # user did their commit with github app
|
||||
return "@" + stripped_fallback
|
||||
else: # exception and edge cases
|
||||
return "@" + stripped_fallback
|
||||
|
||||
if stripped is None: # if commit hash can't find AKA commit is only in fork
|
||||
return "@" + stripped_fallback
|
||||
|
||||
return "@" + stripped
|
||||
def print_nightly_changelog():
|
||||
# Calculate the date and time 24 hours ago
|
||||
since_time = (datetime.now(timezone.utc) - timedelta(hours=24)).isoformat() # Check that this is UTC
|
||||
url = "commits"
|
||||
commits = handle_get_request(url, since_time)
|
||||
for commit in commits:
|
||||
# Print the commit details
|
||||
print(format_output(commit))
|
||||
|
||||
|
||||
def compile_line(line):
|
||||
username = compute_username(line)
|
||||
line = re.sub(r'[*].*[*]', "", line)
|
||||
line = line.replace("{USERNAME}", username)
|
||||
line = re.sub(r'\^', '', line, count=2)
|
||||
|
||||
return line
|
||||
|
||||
|
||||
def extract_first_login(json_data):
|
||||
if not json_data: # if returned null
|
||||
return None
|
||||
|
||||
try:
|
||||
data = json.loads(json_data)
|
||||
|
||||
if isinstance(data, list) and len(data) > 0:
|
||||
first_object = data[0]
|
||||
if 'author' in first_object and 'login' in first_object['author']:
|
||||
return first_object['author']['login']
|
||||
|
||||
except HTTPError as e:
|
||||
if e.code == 403 and "API rate limit exceeded" in str(e):
|
||||
return False
|
||||
except json.decoder.JSONDecodeError:
|
||||
return None
|
||||
def handle_get_request(path, offset=None):
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
params = {"since": offset}
|
||||
url_base = f"https://api.github.com/repos/{repo_owner}/{repo_name}/"
|
||||
response = requests.get(url_base + path, headers=headers, params=params)
|
||||
|
||||
# Check if the request was successful (status code 200)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
print(f"Request failed with status code: {response.status_code}")
|
||||
return None
|
||||
|
||||
|
||||
for row in raw_git.splitlines():
|
||||
print(compile_line(row))
|
||||
def format_output(commit):
|
||||
message_lines = commit["commit"]["message"].split("\n")
|
||||
author = commit["author"]["login"] if commit["author"] and "login" in commit["author"] else commit["commit"]["author"]["name"]
|
||||
return '- ' + commit["sha"][:8] + ' - @' + author + ': ' + message_lines[0]
|
||||
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print_nightly_changelog()
|
||||
else:
|
||||
past_version = sys.argv[1]
|
||||
print_stable_changelog(past_version)
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
run: |
|
||||
CHANGELOG=$(python3 .github/workflows/stable_changelog.py ${{ steps.past_version.outputs.past_version }})
|
||||
CHANGELOG=$(python3 .github/workflows/changelog.py ${{ steps.past_version.outputs.past_version }})
|
||||
CHANGELOG="${CHANGELOG//'%'/'%25'}"
|
||||
CHANGELOG="${CHANGELOG//$'\n'/'%0A'}"
|
||||
CHANGELOG="${CHANGELOG//$'\r'/'%0D'}"
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
from urllib.error import HTTPError
|
||||
|
||||
past_version = sys.argv[1]
|
||||
|
||||
main_repo_url = "https://github.com/eried/portapack-mayhem.git"
|
||||
main_repo_identifier = "eried/portapack-mayhem"
|
||||
# ^^&^ change these if main repo changed
|
||||
|
||||
os.system('gh repo set-default ' + main_repo_url)
|
||||
|
||||
raw_git = os.popen('git log ' + past_version + '..next --pretty=format:"- ^%h^ - {USERNAME}*_%al_%an*: %s"').read()
|
||||
|
||||
|
||||
# ^ as github's rule, a real username can contains "-" but not "_" and "*", so use these two to seperate things
|
||||
|
||||
# during test at 2023-07-10, %al could return 3 different types of values:
|
||||
# 123+abc <<<< abc is real username
|
||||
# def <<<< def is 3rd level domain of unhidden email, drop this, fetch info from github cli client
|
||||
# note that github will probably change in the future
|
||||
|
||||
def compute_username(line):
|
||||
line_org = line
|
||||
stripped = re.search(r'(?<=\*)(.*?)(?=\*)', line).group(0) # get the string between "*" and "*"
|
||||
now_item_hash = re.search(r'(?<=\^)(.*?)(?=\^)', line).group(0) # get the string between "*" and "*"
|
||||
stripped_org = stripped
|
||||
stripped = re.search(r'(?<=\_)(.*?)(?=\_)', stripped).group(0) # get the string between "_" and "_"
|
||||
stripped_fallback = stripped
|
||||
# now it's like "123+abc" or "def"
|
||||
|
||||
if ("+" in stripped): # 123+abc
|
||||
stripped = re.sub(r'^.*?\+', "", stripped)
|
||||
elif not ("+" in stripped): # maybe not real username, dropped, fetch from github cli client
|
||||
fetched_now_item_json = os.popen(
|
||||
'gh search commits repo:' + main_repo_identifier + ' --json author --hash ' + now_item_hash).read()
|
||||
stripped = extract_first_login(fetched_now_item_json)
|
||||
if stripped is False: # 403
|
||||
return "@" + stripped_fallback
|
||||
elif stripped == "app/":
|
||||
return "@" + stripped_fallback
|
||||
else: # exception and edge cases
|
||||
return "@" + stripped_fallback
|
||||
|
||||
if stripped is None: # if commit hash can't find AKA commit is only in fork
|
||||
return "@" + stripped_fallback
|
||||
|
||||
return "@" + stripped
|
||||
|
||||
|
||||
def compile_line(line):
|
||||
username = compute_username(line)
|
||||
line = re.sub(r'[*].*[*]', "", line)
|
||||
line = line.replace("{USERNAME}", username)
|
||||
line = re.sub(r'\^', '', line, count=2)
|
||||
|
||||
return line
|
||||
|
||||
|
||||
def extract_first_login(json_data):
|
||||
if not json_data: # if returned null
|
||||
return None
|
||||
|
||||
try:
|
||||
data = json.loads(json_data)
|
||||
|
||||
if isinstance(data, list) and len(data) > 0:
|
||||
first_object = data[0]
|
||||
if 'author' in first_object and 'login' in first_object['author']:
|
||||
return first_object['author']['login']
|
||||
|
||||
except HTTPError as e:
|
||||
if e.code == 403 and "API rate limit exceeded" in str(e):
|
||||
return False
|
||||
except json.decoder.JSONDecodeError:
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
for row in raw_git.splitlines():
|
||||
print(compile_line(row))
|
||||
@@ -25,10 +25,10 @@
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "file.hpp"
|
||||
#include "freqman.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "capture_app.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
@@ -47,6 +48,14 @@ CaptureAppView::CaptureAppView(NavigationView& nav)
|
||||
&waterfall,
|
||||
});
|
||||
|
||||
/* THE ONE LINE BELOW IS A TEMPORARY KLUDGE WORKAROUND FOR A MYSTERY M4 BASEBAND HANG ISSUE WHEN THE CAPTURE
|
||||
APP IS THE FIRST APP STARTED AFTER POWER-UP, OR THE CAPTURE APP IS RUN AFTER RUNNING REPLAY WITH A HIGH
|
||||
SAMPLE RATE. IT SHOULD NOT BE NECESSARY SINCE sampling_rate IS ALREADY INITIALIZED BY RADIOSTATE BUT
|
||||
APPARENTLY IS ADDING JUST THE RIGHT AMOUNT OF DELAY. ISSUE DOES NOT AFFECT ALL PORTAPACK UNITS.
|
||||
INVESTIGATION IS ONGOING, SEE ISSUE #1283. */
|
||||
receiver_model.set_sampling_rate(3072000);
|
||||
/* END MYSTERY HANG WORKAROUND. */
|
||||
|
||||
field_frequency_step.set_by_value(receiver_model.frequency_step());
|
||||
field_frequency_step.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
receiver_model.set_frequency_step(v);
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "freqman.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
|
||||
@@ -65,12 +65,12 @@ void GpsSimAppView::on_file_changed(const fs::path& new_file_path) {
|
||||
field_frequency.set_value(metadata->center_frequency);
|
||||
sample_rate = metadata->sample_rate;
|
||||
} else {
|
||||
sample_rate = 500000;
|
||||
sample_rate = 2600000;
|
||||
}
|
||||
|
||||
// UI Fixup.
|
||||
text_sample_rate.set(unit_auto_scale(sample_rate, 3, 1) + "Hz");
|
||||
progressbar.set_max(file_size / 1024);
|
||||
progressbar.set_max(file_size);
|
||||
text_filename.set(truncate(file_path.filename().string(), 12));
|
||||
|
||||
auto duration = ms_duration(file_size, sample_rate, 2);
|
||||
|
||||
@@ -26,13 +26,42 @@
|
||||
#include "event_m0.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "rtc_time.hpp"
|
||||
#include "tone_key.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
using namespace portapack;
|
||||
using namespace ui;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// TODO: Clean up after moving to better lookup tables.
|
||||
using options_t = OptionsField::options_t;
|
||||
extern options_t freqman_modulations;
|
||||
extern options_t freqman_bandwidths[4];
|
||||
extern options_t freqman_steps;
|
||||
extern options_t freqman_steps_short;
|
||||
|
||||
/* Set options. */
|
||||
void freqman_set_modulation_option(OptionsField& option) {
|
||||
option.set_options(freqman_modulations);
|
||||
}
|
||||
|
||||
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) {
|
||||
if (is_valid(modulation))
|
||||
option.set_options(freqman_bandwidths[modulation]);
|
||||
}
|
||||
|
||||
void freqman_set_step_option(OptionsField& option) {
|
||||
option.set_options(freqman_steps);
|
||||
}
|
||||
|
||||
void freqman_set_step_option_short(OptionsField& option) {
|
||||
option.set_options(freqman_steps_short);
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
|
||||
/* FreqManBaseView ***************************************/
|
||||
@@ -140,14 +169,14 @@ FrequencySaveView::FrequencySaveView(
|
||||
|
||||
button_edit.on_select = [this, &nav](Button&) {
|
||||
temp_buffer_ = entry_.description;
|
||||
text_prompt(nav_, temp_buffer_, 30, [this](std::string& new_desc) {
|
||||
text_prompt(nav_, temp_buffer_, desc_edit_max, [this](std::string& new_desc) {
|
||||
entry_.description = new_desc;
|
||||
refresh_ui();
|
||||
});
|
||||
};
|
||||
|
||||
button_save.on_select = [this, &nav](Button&) {
|
||||
db_.insert_entry(entry_, db_.entry_count());
|
||||
db_.insert_entry(db_.entry_count(), entry_);
|
||||
nav_.pop();
|
||||
};
|
||||
}
|
||||
@@ -188,8 +217,15 @@ FrequencyLoadView::FrequencyLoadView(
|
||||
|
||||
/* FrequencyManagerView **********************************/
|
||||
|
||||
void FrequencyManagerView::on_edit_entry() {
|
||||
auto edit_view = nav_.push<FrequencyEditView>(current_entry());
|
||||
edit_view->on_save = [this](const freqman_entry& entry) {
|
||||
db_.replace_entry(current_index(), entry);
|
||||
freqlist_view.set_dirty();
|
||||
};
|
||||
}
|
||||
|
||||
void FrequencyManagerView::on_edit_freq() {
|
||||
// TODO: range edit support?
|
||||
auto freq_edit_view = nav_.push<FrequencyKeypadView>(current_entry().frequency_a);
|
||||
freq_edit_view->on_changed = [this](rf::Frequency f) {
|
||||
auto entry = current_entry();
|
||||
@@ -201,7 +237,7 @@ void FrequencyManagerView::on_edit_freq() {
|
||||
|
||||
void FrequencyManagerView::on_edit_desc() {
|
||||
temp_buffer_ = current_entry().description;
|
||||
text_prompt(nav_, temp_buffer_, 28, [this](std::string& new_desc) {
|
||||
text_prompt(nav_, temp_buffer_, desc_edit_max, [this](std::string& new_desc) {
|
||||
auto entry = current_entry();
|
||||
entry.description = std::move(new_desc);
|
||||
db_.replace_entry(current_index(), entry);
|
||||
@@ -211,7 +247,7 @@ void FrequencyManagerView::on_edit_desc() {
|
||||
|
||||
void FrequencyManagerView::on_add_category() {
|
||||
temp_buffer_.clear();
|
||||
text_prompt(nav_, temp_buffer_, 12, [this](std::string& new_name) {
|
||||
text_prompt(nav_, temp_buffer_, 20, [this](std::string& new_name) {
|
||||
if (!new_name.empty()) {
|
||||
create_freqman_file(new_name);
|
||||
refresh_categories();
|
||||
@@ -240,7 +276,7 @@ void FrequencyManagerView::on_add_entry() {
|
||||
};
|
||||
|
||||
// Add will insert below the currently selected item.
|
||||
db_.insert_entry(entry, current_index() + 1);
|
||||
db_.insert_entry(current_index() + 1, entry);
|
||||
refresh_list(1);
|
||||
}
|
||||
|
||||
@@ -249,7 +285,7 @@ void FrequencyManagerView::on_del_entry() {
|
||||
return;
|
||||
|
||||
nav_.push<ModalMessageView>(
|
||||
"Delete", "Delete" + pretty_string(current_entry(), 23) + "\nAre you sure?", YESNO,
|
||||
"Delete", "Delete " + trim(pretty_string(current_entry(), 23)) + "\nAre you sure?", YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice) {
|
||||
db_.delete_entry(current_index());
|
||||
@@ -263,20 +299,21 @@ FrequencyManagerView::FrequencyManagerView(
|
||||
: FreqManBaseView(nav) {
|
||||
add_children(
|
||||
{&freqlist_view,
|
||||
&labels,
|
||||
&button_add_category,
|
||||
&button_del_category,
|
||||
&button_edit_entry,
|
||||
&rect_padding,
|
||||
&button_edit_freq,
|
||||
&button_edit_desc,
|
||||
&button_add_entry,
|
||||
&button_del_entry});
|
||||
|
||||
freqlist_view.on_select = [this](size_t) {
|
||||
button_edit_freq.focus();
|
||||
button_edit_entry.focus();
|
||||
};
|
||||
// Allows for quickly exiting control.
|
||||
freqlist_view.on_leave = [this]() {
|
||||
button_edit_freq.focus();
|
||||
button_edit_entry.focus();
|
||||
};
|
||||
|
||||
button_add_category.on_select = [this]() {
|
||||
@@ -287,6 +324,10 @@ FrequencyManagerView::FrequencyManagerView(
|
||||
on_del_category();
|
||||
};
|
||||
|
||||
button_edit_entry.on_select = [this](Button&) {
|
||||
on_edit_entry();
|
||||
};
|
||||
|
||||
button_edit_freq.on_select = [this](Button&) {
|
||||
on_edit_freq();
|
||||
};
|
||||
@@ -304,4 +345,180 @@ FrequencyManagerView::FrequencyManagerView(
|
||||
};
|
||||
}
|
||||
|
||||
/* FrequencyEditView *************************************/
|
||||
|
||||
FrequencyEditView::FrequencyEditView(
|
||||
NavigationView& nav,
|
||||
freqman_entry entry)
|
||||
: nav_{nav},
|
||||
entry_{std::move(entry)} {
|
||||
add_children({&labels,
|
||||
&field_type,
|
||||
&field_freq_a,
|
||||
&field_freq_b,
|
||||
&field_modulation,
|
||||
&field_bandwidth,
|
||||
&field_step,
|
||||
&field_tone,
|
||||
&field_description,
|
||||
&text_validation,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
freqman_set_modulation_option(field_modulation);
|
||||
populate_bandwidth_options();
|
||||
populate_step_options();
|
||||
populate_tone_options();
|
||||
|
||||
// Add the "invalid/unset" option. Kind of hacky, but...
|
||||
field_modulation.options().insert(
|
||||
field_modulation.options().begin(), {"None", -1});
|
||||
field_step.options().insert(
|
||||
field_step.options().begin(), {"None", -1});
|
||||
|
||||
field_type.set_by_value((int32_t)entry_.type);
|
||||
field_type.on_change = [this](size_t, auto value) {
|
||||
entry_.type = static_cast<freqman_type>(value);
|
||||
refresh_ui();
|
||||
};
|
||||
|
||||
// TODO: this pattern should be able to be wrapped up.
|
||||
field_freq_a.set_value(entry_.frequency_a);
|
||||
field_freq_a.on_change = [this](rf::Frequency f) {
|
||||
entry_.frequency_a = f;
|
||||
refresh_ui();
|
||||
};
|
||||
field_freq_a.on_edit = [this]() {
|
||||
auto freq_view = nav_.push<FrequencyKeypadView>(field_freq_a.value());
|
||||
freq_view->on_changed = [this](rf::Frequency f) {
|
||||
field_freq_a.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
field_freq_b.set_value(entry_.frequency_b);
|
||||
field_freq_b.on_change = [this](rf::Frequency f) {
|
||||
entry_.frequency_b = f;
|
||||
refresh_ui();
|
||||
};
|
||||
field_freq_b.on_edit = [this]() {
|
||||
auto freq_view = nav_.push<FrequencyKeypadView>(field_freq_b.value());
|
||||
freq_view->on_changed = [this](rf::Frequency f) {
|
||||
field_freq_b.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
field_modulation.set_by_value((int32_t)entry_.modulation);
|
||||
field_modulation.on_change = [this](size_t, auto value) {
|
||||
entry_.modulation = static_cast<freqman_index_t>(value);
|
||||
populate_bandwidth_options();
|
||||
};
|
||||
|
||||
field_bandwidth.set_by_value((int32_t)entry_.bandwidth);
|
||||
field_bandwidth.on_change = [this](size_t, auto value) {
|
||||
entry_.bandwidth = static_cast<freqman_index_t>(value);
|
||||
};
|
||||
|
||||
field_step.set_by_value((int32_t)entry_.step);
|
||||
field_step.on_change = [this](size_t, auto value) {
|
||||
entry_.step = static_cast<freqman_index_t>(value);
|
||||
};
|
||||
|
||||
field_tone.set_by_value((int32_t)entry_.tone);
|
||||
field_tone.on_change = [this](size_t, auto value) {
|
||||
entry_.tone = static_cast<freqman_index_t>(value);
|
||||
};
|
||||
|
||||
field_description.set_text(entry_.description);
|
||||
field_description.on_change = [this](TextField& tf) {
|
||||
entry_.description = tf.get_text();
|
||||
};
|
||||
field_description.on_select = [this](TextField& tf) {
|
||||
temp_buffer_ = tf.get_text();
|
||||
text_prompt(nav_, temp_buffer_, FreqManBaseView::desc_edit_max,
|
||||
[this, &tf](std::string& new_desc) {
|
||||
tf.set_text(new_desc);
|
||||
});
|
||||
};
|
||||
|
||||
button_save.on_select = [this](Button&) {
|
||||
if (on_save)
|
||||
on_save(std::move(entry_));
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [this](Button&) {
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
refresh_ui();
|
||||
}
|
||||
|
||||
void FrequencyEditView::focus() {
|
||||
button_cancel.focus();
|
||||
}
|
||||
|
||||
void FrequencyEditView::refresh_ui() {
|
||||
// This needs to be called whenever a UI option is changed
|
||||
// in a way that causes fields to be unused or would make the
|
||||
// entry fail validation.
|
||||
|
||||
auto is_range = entry_.type == freqman_type::Range;
|
||||
auto is_ham = entry_.type == freqman_type::HamRadio;
|
||||
auto has_freq_b = is_range || is_ham;
|
||||
|
||||
field_freq_b.set_style(has_freq_b ? &Styles::white : &Styles::grey);
|
||||
field_step.set_style(is_range ? &Styles::white : &Styles::grey);
|
||||
field_tone.set_style(is_ham ? &Styles::white : &Styles::grey);
|
||||
|
||||
if (is_valid(entry_)) {
|
||||
text_validation.set("Valid");
|
||||
text_validation.set_style(&Styles::green);
|
||||
} else {
|
||||
text_validation.set("Error");
|
||||
text_validation.set_style(&Styles::red);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: The following functions shouldn't be needed once
|
||||
// freqman_db lookup tables are complete.
|
||||
void FrequencyEditView::populate_bandwidth_options() {
|
||||
OptionsField::options_t options;
|
||||
options.push_back({"None", -1});
|
||||
|
||||
if (entry_.modulation < std::size(freqman_bandwidths)) {
|
||||
auto& bandwidths = freqman_bandwidths[entry_.modulation];
|
||||
for (auto i = 1u; i < bandwidths.size(); ++i) {
|
||||
auto& item = bandwidths[i];
|
||||
options.push_back({item.first, (OptionsField::value_t)i});
|
||||
}
|
||||
}
|
||||
|
||||
field_bandwidth.set_options(std::move(options));
|
||||
}
|
||||
|
||||
void FrequencyEditView::populate_step_options() {
|
||||
OptionsField::options_t options;
|
||||
options.push_back({"None", -1});
|
||||
|
||||
for (auto i = 1u; i < freqman_steps.size(); ++i) {
|
||||
auto& item = freqman_steps[i];
|
||||
options.push_back({item.first, (OptionsField::value_t)i});
|
||||
}
|
||||
|
||||
field_step.set_options(std::move(options));
|
||||
}
|
||||
|
||||
void FrequencyEditView::populate_tone_options() {
|
||||
using namespace tonekey;
|
||||
OptionsField::options_t options;
|
||||
options.push_back({"None", -1});
|
||||
|
||||
for (auto i = 1u; i < tone_keys.size(); ++i) {
|
||||
auto& item = tone_keys[i];
|
||||
options.push_back({fx100_string(item.second), (OptionsField::value_t)i});
|
||||
}
|
||||
|
||||
field_tone.set_options(std::move(options));
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -36,17 +36,18 @@ namespace ui {
|
||||
|
||||
class FreqManBaseView : public View {
|
||||
public:
|
||||
FreqManBaseView(
|
||||
NavigationView& nav);
|
||||
FreqManBaseView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
static constexpr size_t desc_edit_max = 0x80;
|
||||
|
||||
protected:
|
||||
using options_t = OptionsField::options_t;
|
||||
|
||||
NavigationView& nav_;
|
||||
freqman_error error_{NO_ERROR};
|
||||
std::function<void(void)> on_select_frequency{nullptr};
|
||||
std::function<void(void)> on_select_frequency{};
|
||||
|
||||
void change_category(size_t new_index);
|
||||
/* Access the categories directly from the OptionsField.
|
||||
@@ -85,7 +86,7 @@ class FreqManBaseView : public View {
|
||||
class FrequencySaveView : public FreqManBaseView {
|
||||
public:
|
||||
FrequencySaveView(NavigationView& nav, const rf::Frequency value);
|
||||
std::string title() const override { return "Save freq"; };
|
||||
std::string title() const override { return "Save freq"; }
|
||||
|
||||
private:
|
||||
std::string temp_buffer_{};
|
||||
@@ -121,17 +122,18 @@ class FrequencyLoadView : public FreqManBaseView {
|
||||
std::function<void(rf::Frequency, rf::Frequency)> on_range_loaded{};
|
||||
|
||||
FrequencyLoadView(NavigationView& nav);
|
||||
std::string title() const override { return "Load freq"; };
|
||||
std::string title() const override { return "Load freq"; }
|
||||
};
|
||||
|
||||
class FrequencyManagerView : public FreqManBaseView {
|
||||
public:
|
||||
FrequencyManagerView(NavigationView& nav);
|
||||
std::string title() const override { return "Freqman"; };
|
||||
std::string title() const override { return "Freqman"; }
|
||||
|
||||
private:
|
||||
std::string temp_buffer_{};
|
||||
|
||||
void on_edit_entry();
|
||||
void on_edit_freq();
|
||||
void on_edit_desc();
|
||||
void on_add_category();
|
||||
@@ -139,9 +141,6 @@ class FrequencyManagerView : public FreqManBaseView {
|
||||
void on_add_entry();
|
||||
void on_del_entry();
|
||||
|
||||
Labels labels{
|
||||
{{5 * 8, 14 * 16 - 4}, "Edit:", Color::light_grey()}};
|
||||
|
||||
NewButton button_add_category{
|
||||
{23 * 8, 0 * 16, 7 * 4, 20},
|
||||
{},
|
||||
@@ -156,6 +155,14 @@ class FrequencyManagerView : public FreqManBaseView {
|
||||
Color::red(),
|
||||
true};
|
||||
|
||||
Button button_edit_entry{
|
||||
{0 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
|
||||
"Edit"};
|
||||
|
||||
Rectangle rect_padding{
|
||||
{15 * 8, 14 * 16 - 4, 15 * 8, 1 * 16 + 4},
|
||||
Color::grey()};
|
||||
|
||||
Button button_edit_freq{
|
||||
{0 * 8, 15 * 16, 15 * 8, 2 * 16},
|
||||
"Frequency"};
|
||||
@@ -179,4 +186,79 @@ class FrequencyManagerView : public FreqManBaseView {
|
||||
true};
|
||||
};
|
||||
|
||||
class FrequencyEditView : public View {
|
||||
public:
|
||||
std::function<void(const freqman_entry&)> on_save{};
|
||||
|
||||
FrequencyEditView(
|
||||
NavigationView& nav,
|
||||
freqman_entry entry);
|
||||
std::string title() const override { return "Freqman Edit"; }
|
||||
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
freqman_entry entry_;
|
||||
std::string temp_buffer_{};
|
||||
|
||||
void refresh_ui();
|
||||
void populate_bandwidth_options();
|
||||
void populate_step_options();
|
||||
void populate_tone_options();
|
||||
|
||||
Labels labels{
|
||||
{{5 * 8, 1 * 16}, "Edit Frequency Entry", Color::white()},
|
||||
{{0 * 8, 3 * 16}, "Entry Type :", Color::light_grey()},
|
||||
{{0 * 8, 4 * 16}, "Frequency A:", Color::light_grey()},
|
||||
{{0 * 8, 5 * 16}, "Frequency B:", Color::light_grey()},
|
||||
{{0 * 8, 6 * 16}, "Modulation :", Color::light_grey()},
|
||||
{{0 * 8, 7 * 16}, "Bandwidth :", Color::light_grey()},
|
||||
{{0 * 8, 8 * 16}, "Step :", Color::light_grey()},
|
||||
{{0 * 8, 9 * 16}, "Tone Freq :", Color::light_grey()},
|
||||
{{0 * 8, 10 * 16}, "Description:", Color::light_grey()},
|
||||
};
|
||||
|
||||
OptionsField field_type{{13 * 8, 3 * 16}, 8, {
|
||||
{"Single", 0},
|
||||
{"Range", 1},
|
||||
{"HamRadio", 2},
|
||||
{"Raw", 3},
|
||||
}};
|
||||
|
||||
FrequencyField field_freq_a{{13 * 8, 4 * 16}};
|
||||
|
||||
FrequencyField field_freq_b{{13 * 8, 5 * 16}};
|
||||
|
||||
OptionsField field_modulation{{13 * 8, 6 * 16}, 5, {}};
|
||||
|
||||
OptionsField field_bandwidth{{13 * 8, 7 * 16}, 7, {}};
|
||||
|
||||
OptionsField field_step{{13 * 8, 8 * 16}, 12, {}};
|
||||
|
||||
OptionsField field_tone{{13 * 8, 9 * 16}, 13, {}};
|
||||
|
||||
TextField field_description{
|
||||
{13 * 8, 10 * 16, 17 * 8, 1 * 16},
|
||||
{}};
|
||||
|
||||
Text text_validation{
|
||||
{12 * 8, 12 * 16, 5 * 8, 1 * 16},
|
||||
{}};
|
||||
|
||||
Button button_save{
|
||||
{0 * 8, 17 * 16, 15 * 8, 2 * 16},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{15 * 8, 17 * 16, 15 * 8, 2 * 16},
|
||||
"Cancel"};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
void freqman_set_bandwidth_option(freqman_index_t modulation, ui::OptionsField& option);
|
||||
void freqman_set_modulation_option(ui::OptionsField& option);
|
||||
void freqman_set_step_option(ui::OptionsField& option);
|
||||
void freqman_set_step_option_short(ui::OptionsField& option);
|
||||
void freqman_set_tone_option(ui::OptionsField& option);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "ui_level.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "file.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
@@ -24,21 +24,21 @@
|
||||
#ifndef _UI_LEVEL
|
||||
#define _UI_LEVEL
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "receiver_model.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "freqman.hpp"
|
||||
#include "analog_audio_app.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "ui_mictx.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "file.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "file.hpp"
|
||||
#include "freqman_db.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "receiver_model.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui.hpp"
|
||||
#include "ui_mictx.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_spectrum.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "portapack_hal.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "tonesets.hpp"
|
||||
@@ -355,13 +356,10 @@ MicTXView::MicTXView(
|
||||
rxaudio(rx_enabled); // Update now if we have RX audio on
|
||||
options_tone_key.hidden(0); // we are in FM mode, we should have active the Key-tones & CTCSS option.
|
||||
|
||||
rxbw.emplace_back(" NFM1:8k5 ", 0); // restore the original dynamic field_rxbw value.
|
||||
rxbw.emplace_back(" NFM2:11k ", 1);
|
||||
rxbw.emplace_back(" FM :16k ", 2);
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
field_rxbw.hidden(0); // we are in FM mode, we need to allow the user set up of the RX NFM BW selection (8K5, 11K, 16K)
|
||||
field_bw.hidden(0); // we are in FM mode, we need to allow FM deviation parameter, in non FM mode.
|
||||
freqman_set_bandwidth_option(NFM_MODULATION, field_rxbw); // restore dynamic field_rxbw value with NFM options from freqman_db.cpp
|
||||
field_rxbw.set_by_value(2); // // bw 16k (2) default
|
||||
field_rxbw.hidden(0); // we are in FM mode, we need to allow the user set up of the RX NFM BW selection (8K5, 11K, 16K)
|
||||
field_bw.hidden(0); // we are in FM mode, we need to allow FM deviation parameter, in non FM mode.
|
||||
break;
|
||||
case 1: //{ "WFM", 1 }
|
||||
enable_am = false;
|
||||
@@ -375,13 +373,10 @@ MicTXView::MicTXView(
|
||||
rxaudio(rx_enabled); // Update now if we have RX audio on
|
||||
options_tone_key.hidden(0); // we are in WFM mode, we should have active the Key-tones & CTCSS option.
|
||||
|
||||
rxbw.emplace_back(" 200k-WFM ", 0); // We allow the user selection of the 3 x WFM BW filters, (0) WFM-200K, (1) WFM-180K, (2) WFM-40K.
|
||||
rxbw.emplace_back(" 180k-WFM ", 1);
|
||||
rxbw.emplace_back(" 40k-WFM ", 2);
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
field_rxbw.hidden(0); // we are in WFM mode, we need to show to the user the selected BW WFM filter.
|
||||
field_bw.hidden(0); // we are in WFM mode, we need to allow WFM deviation parameter, in non FM mode.
|
||||
freqman_set_bandwidth_option(WFM_MODULATION, field_rxbw); // restore dynamic field_rxbw value with WFM options from freqman_db.cpp
|
||||
field_rxbw.set_by_value(0); // bw 200k (0) default
|
||||
field_rxbw.hidden(0); // we are in WFM mode, we need to show to the user the selected BW WFM filter.
|
||||
field_bw.hidden(0); // we are in WFM mode, we need to allow WFM deviation parameter, in non FM mode.
|
||||
break;
|
||||
case 2: //{ "AM", 2 }
|
||||
enable_am = true;
|
||||
@@ -390,8 +385,8 @@ MicTXView::MicTXView(
|
||||
set_dirty(); // Refresh display
|
||||
options_tone_key.hidden(1); // we hide that Key-tones & CTCSS input selecction, (no meaning in AM/DSB/SSB).
|
||||
|
||||
rxbw.emplace_back(" DSB1-9k ", 0); // we offer in AM DSB two audio BW 9k / 6k.
|
||||
rxbw.emplace_back(" DSB2-6k ", 1);
|
||||
rxbw.emplace_back("DSB 9k", 0); // we offer in AM DSB two audio BW 9k / 6k.
|
||||
rxbw.emplace_back("DSB 6k", 1);
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
field_rxbw.hidden(0); // we show fixed RX AM BW 6Khz
|
||||
@@ -404,8 +399,8 @@ MicTXView::MicTXView(
|
||||
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB, by now.
|
||||
check_rogerbeep.hidden(1); // hide that roger beep selection.
|
||||
|
||||
rxbw.emplace_back(" USB+3k ", 0); // locked a fixed option, to display it.
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
rxbw.emplace_back("USB+3k", 0); // locked a fixed option, to display it.
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
set_dirty(); // Refresh display
|
||||
break;
|
||||
@@ -415,8 +410,8 @@ MicTXView::MicTXView(
|
||||
check_rogerbeep.set_value(false); // reset the possible activation of roger beep, because it is not compatible with SSB, by now.
|
||||
check_rogerbeep.hidden(1); // hide that roger beep selection.
|
||||
|
||||
rxbw.emplace_back(" LSB-3k ", 0); // locked a fixed option, to display it.
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
rxbw.emplace_back("LSB-3k", 0); // locked a fixed option, to display it.
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
set_dirty(); // Refresh display
|
||||
break;
|
||||
@@ -425,8 +420,8 @@ MicTXView::MicTXView(
|
||||
rxaudio(rx_enabled); // Update now if we have RX audio on
|
||||
check_rogerbeep.hidden(0); // make visible again the "rogerbeep" selection.
|
||||
|
||||
rxbw.emplace_back("SSB1:USB+3k", 0); // added dynamically two options (index 0,1) to that DSB-C case to the field_rxbw value.
|
||||
rxbw.emplace_back("SSB2:LSB-3k", 1);
|
||||
rxbw.emplace_back("USB+3k", 0); // added dynamically two options (index 0,1) to that DSB-SC case to the field_rxbw value.
|
||||
rxbw.emplace_back("LSB-3k", 1);
|
||||
|
||||
field_rxbw.set_options(rxbw); // store that aux GUI option to the field_rxbw.
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ class MicTXView : public View {
|
||||
|
||||
OptionsField options_ak4951_alc_mode{
|
||||
{20 * 8, 1 * 8},
|
||||
11,
|
||||
10, // Label has 10 chars
|
||||
{
|
||||
{" OFF-12kHz", 0}, // Nothing changed from ORIGINAL, keeping ALL programmable AK4951 Digital Block->OFF, sampling 24Khz)
|
||||
{"+12dB-6kHz", 1}, // ALC-> on, (+12dB's) Auto Vol max + Wind Noise cancel + LPF 6kHz + Pre-amp Mic (+21dB=original)
|
||||
@@ -198,7 +198,7 @@ class MicTXView : public View {
|
||||
|
||||
OptionsField options_wm8731_boost_mode{
|
||||
{22 * 8, 1 * 8},
|
||||
5,
|
||||
8, // Label has 8 chars
|
||||
{
|
||||
{"ON +12dB", 0}, // WM8731 Mic Boost ON, original+12dBs condition, easy to saturate ADC sat in high voice, relative G = +12 dB's respect ref level
|
||||
{"ON +06dB", 1}, // WM8731 Mic Boost ON, original+6 dBs condition, easy to saturate ADC sat in high voice, relative G = +06 dB's respect ref level
|
||||
@@ -298,11 +298,11 @@ class MicTXView : public View {
|
||||
|
||||
OptionsField field_rxbw{
|
||||
{19 * 8, (23 * 8) + 2},
|
||||
3,
|
||||
7,
|
||||
{
|
||||
{" NFM1:8k5 ", 0},
|
||||
{" NFM2:11k ", 1},
|
||||
{" FM :16k ", 2},
|
||||
{" 8k5 ", 0}, // Initial dynamic values when we start Mic App.
|
||||
{" 11k ", 1},
|
||||
{" 16k ", 2},
|
||||
}};
|
||||
|
||||
NumberField field_squelch{
|
||||
|
||||
@@ -23,13 +23,17 @@
|
||||
|
||||
#include "ui_recon.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
#include "capture_app.hpp"
|
||||
#include "convert.hpp"
|
||||
#include "file.hpp"
|
||||
#include "file_reader.hpp"
|
||||
#include "tone_key.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace tonekey;
|
||||
using portapack::memory::map::backup_ram;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -124,152 +128,81 @@ void ReconView::colorize_waits() {
|
||||
}
|
||||
}
|
||||
|
||||
bool ReconView::recon_save_freq(const std::string& freq_file_path, size_t freq_index, bool warn_if_exists) {
|
||||
File recon_file;
|
||||
|
||||
bool ReconView::recon_save_freq(const fs::path& path, size_t freq_index, bool warn_if_exists) {
|
||||
if (frequency_list.size() == 0 || !current_is_valid())
|
||||
return false;
|
||||
|
||||
FreqmanDB freq_db;
|
||||
if (!freq_db.open(path, /*create*/ true))
|
||||
return false;
|
||||
|
||||
freqman_entry entry = *frequency_list[freq_index]; // Makes a copy.
|
||||
entry.frequency_a = freq;
|
||||
entry.frequency_b = 0;
|
||||
entry.modulation = last_entry.modulation;
|
||||
entry.bandwidth = last_entry.bandwidth;
|
||||
entry.type = freqman_type::Single;
|
||||
|
||||
// TODO: Use FreqmanDB
|
||||
auto frequency_to_add = to_freqman_string(entry);
|
||||
|
||||
auto result = recon_file.open(freq_file_path); // First recon if freq is already in txt
|
||||
if (!result.is_valid()) {
|
||||
char one_char[1]{}; // Read it char by char
|
||||
std::string line{}; // and put read line in here
|
||||
bool found{false};
|
||||
for (size_t pointer = 0; pointer < recon_file.size(); pointer++) {
|
||||
recon_file.seek(pointer);
|
||||
recon_file.read(one_char, 1);
|
||||
if ((int)one_char[0] > 31) { // ascii space upwards
|
||||
line += one_char[0]; // add it to the textline
|
||||
} else if (one_char[0] == '\n') { // New Line
|
||||
if (line.compare(0, frequency_to_add.size(), frequency_to_add) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
line.clear(); // Ready for next textline
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
result = recon_file.append(freq_file_path); // Second: append if it is not there
|
||||
if (!result.is_valid()) {
|
||||
recon_file.write_line(frequency_to_add);
|
||||
}
|
||||
}
|
||||
if (found && warn_if_exists) {
|
||||
nav_.display_modal("Error", "Frequency already exists");
|
||||
}
|
||||
} else {
|
||||
result = recon_file.create(freq_file_path); // First freq if no output file
|
||||
if (!result.is_valid()) {
|
||||
recon_file.write_line(frequency_to_add);
|
||||
}
|
||||
// For ranges, save the current frequency instead.
|
||||
if (entry.type == freqman_type::Range) {
|
||||
entry.frequency_a = freq;
|
||||
entry.frequency_b = 0;
|
||||
entry.type = freqman_type::Single;
|
||||
entry.modulation = last_entry.modulation;
|
||||
entry.bandwidth = last_entry.bandwidth;
|
||||
entry.step = freqman_invalid_index;
|
||||
}
|
||||
|
||||
auto it = freq_db.find_entry(entry);
|
||||
auto found = (it != freq_db.end());
|
||||
|
||||
if (found && warn_if_exists)
|
||||
nav_.display_modal("Error", "Frequency already exists");
|
||||
|
||||
if (!found)
|
||||
freq_db.append_entry(entry);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReconView::recon_load_config_from_sd() {
|
||||
File settings_file{};
|
||||
size_t length{0};
|
||||
size_t file_position{0};
|
||||
char* pos{NULL};
|
||||
char* line_start{NULL};
|
||||
char* line_end{NULL};
|
||||
char file_data[257]{};
|
||||
uint32_t it{0};
|
||||
uint32_t nb_params{RECON_SETTINGS_NB_PARAMS};
|
||||
std::string params[RECON_SETTINGS_NB_PARAMS]{};
|
||||
|
||||
make_new_directory(u"SETTINGS");
|
||||
|
||||
auto result = settings_file.open(RECON_CFG_FILE);
|
||||
if (!result.is_valid()) {
|
||||
while (it < nb_params) {
|
||||
// Read a 256 bytes block from file
|
||||
settings_file.seek(file_position);
|
||||
memset(file_data, 0, 257);
|
||||
auto read_size = settings_file.read(file_data, 256);
|
||||
if (read_size.is_error())
|
||||
break;
|
||||
file_position += 256;
|
||||
// Reset line_start to beginning of buffer
|
||||
line_start = file_data;
|
||||
pos = line_start;
|
||||
while ((line_end = strstr(line_start, "\x0A"))) {
|
||||
length = line_end - line_start - 1;
|
||||
params[it] = std::string(pos, length);
|
||||
it++;
|
||||
line_start = line_end + 1;
|
||||
pos = line_start;
|
||||
if (line_start - file_data >= 256)
|
||||
break;
|
||||
if (it >= nb_params)
|
||||
break;
|
||||
}
|
||||
if (read_size.value() != 256)
|
||||
break; // End of file
|
||||
|
||||
// Restart at beginning of last incomplete line
|
||||
file_position -= (file_data + 256 - line_start);
|
||||
}
|
||||
}
|
||||
|
||||
if (it < nb_params) {
|
||||
/* bad number of params, signal defaults */
|
||||
input_file = "RECON";
|
||||
output_file = "RECON_RESULTS";
|
||||
recon_lock_duration = RECON_MIN_LOCK_DURATION;
|
||||
recon_lock_nb_match = RECON_DEF_NB_MATCH;
|
||||
squelch = -14;
|
||||
recon_match_mode = RECON_MATCH_CONTINUOUS;
|
||||
wait = RECON_DEF_WAIT_DURATION;
|
||||
File settings_file;
|
||||
auto error = settings_file.open(RECON_CFG_FILE);
|
||||
if (error)
|
||||
return false;
|
||||
|
||||
auto complete = false;
|
||||
auto line_nb = 0;
|
||||
auto reader = FileLineReader(settings_file);
|
||||
for (const auto& line : reader) {
|
||||
switch (line_nb) {
|
||||
case 0:
|
||||
input_file = trim(line);
|
||||
break;
|
||||
case 1:
|
||||
output_file = trim(line);
|
||||
break;
|
||||
case 2:
|
||||
parse_int(line, recon_lock_duration);
|
||||
break;
|
||||
case 3:
|
||||
parse_int(line, recon_lock_nb_match);
|
||||
break;
|
||||
case 4:
|
||||
parse_int(line, squelch);
|
||||
break;
|
||||
case 5:
|
||||
parse_int(line, recon_match_mode);
|
||||
break;
|
||||
case 6:
|
||||
parse_int(line, recon_match_mode);
|
||||
complete = true; // NB: Last entry.
|
||||
break;
|
||||
}
|
||||
|
||||
if (complete) break;
|
||||
|
||||
line_nb++;
|
||||
}
|
||||
|
||||
if (it > 0)
|
||||
input_file = params[0];
|
||||
else
|
||||
input_file = "RECON";
|
||||
|
||||
if (it > 1)
|
||||
output_file = params[1];
|
||||
else
|
||||
output_file = "RECON_RESULTS";
|
||||
|
||||
if (it > 2)
|
||||
recon_lock_duration = strtoll(params[2].c_str(), nullptr, 10);
|
||||
else
|
||||
recon_lock_duration = RECON_MIN_LOCK_DURATION;
|
||||
|
||||
if (it > 3)
|
||||
recon_lock_nb_match = strtoll(params[3].c_str(), nullptr, 10);
|
||||
else
|
||||
recon_lock_nb_match = RECON_DEF_NB_MATCH;
|
||||
|
||||
if (it > 4)
|
||||
squelch = strtoll(params[4].c_str(), nullptr, 10);
|
||||
else
|
||||
squelch = -14;
|
||||
|
||||
if (it > 5)
|
||||
recon_match_mode = strtoll(params[5].c_str(), nullptr, 10);
|
||||
else
|
||||
recon_match_mode = RECON_MATCH_CONTINUOUS;
|
||||
|
||||
if (it > 6)
|
||||
wait = strtoll(params[6].c_str(), nullptr, 10);
|
||||
else
|
||||
wait = RECON_DEF_WAIT_DURATION;
|
||||
|
||||
return true;
|
||||
return complete;
|
||||
}
|
||||
|
||||
bool ReconView::recon_save_config_to_sd() {
|
||||
@@ -486,6 +419,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
if (frequency_list.size() > 0) {
|
||||
auto new_view = nav_.push<FrequencyKeypadView>(current_index);
|
||||
new_view->on_changed = [this, &button](rf::Frequency f) {
|
||||
// NB: This is using the freq keypad to select an index.
|
||||
f = f / OneMHz;
|
||||
if (f >= 1 && f <= frequency_list.size()) {
|
||||
index_stepper = f - 1 - current_index;
|
||||
@@ -496,17 +430,18 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_manual_start.on_change = [this]() {
|
||||
frequency_range.min = frequency_range.min + button_manual_start.get_encoder_delta() * freqman_entry_get_step_value(def_step);
|
||||
auto step_val = freqman_entry_get_step_value(def_step);
|
||||
frequency_range.min = frequency_range.min + button_manual_start.get_encoder_delta() * step_val;
|
||||
if (frequency_range.min < 0) {
|
||||
frequency_range.min = 0;
|
||||
}
|
||||
if (frequency_range.min > (MAX_UFREQ - freqman_entry_get_step_value(def_step))) {
|
||||
frequency_range.min = MAX_UFREQ - freqman_entry_get_step_value(def_step);
|
||||
if (frequency_range.min > (MAX_UFREQ - step_val)) {
|
||||
frequency_range.min = MAX_UFREQ - step_val;
|
||||
}
|
||||
if (frequency_range.min > (frequency_range.max - freqman_entry_get_step_value(def_step))) {
|
||||
frequency_range.max = frequency_range.min + freqman_entry_get_step_value(def_step);
|
||||
if (frequency_range.min > (frequency_range.max - step_val)) {
|
||||
frequency_range.max = frequency_range.min + step_val;
|
||||
if (frequency_range.max > MAX_UFREQ) {
|
||||
frequency_range.min = MAX_UFREQ - freqman_entry_get_step_value(def_step);
|
||||
frequency_range.min = MAX_UFREQ - step_val;
|
||||
frequency_range.max = MAX_UFREQ;
|
||||
}
|
||||
}
|
||||
@@ -516,18 +451,19 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_manual_end.on_change = [this]() {
|
||||
frequency_range.max = frequency_range.max + button_manual_end.get_encoder_delta() * freqman_entry_get_step_value(def_step);
|
||||
if (frequency_range.max < (freqman_entry_get_step_value(def_step) + 1)) {
|
||||
frequency_range.max = (freqman_entry_get_step_value(def_step) + 1);
|
||||
auto step_val = freqman_entry_get_step_value(def_step);
|
||||
frequency_range.max = frequency_range.max + button_manual_end.get_encoder_delta() * step_val;
|
||||
if (frequency_range.max < (step_val + 1)) {
|
||||
frequency_range.max = (step_val + 1);
|
||||
}
|
||||
if (frequency_range.max > MAX_UFREQ) {
|
||||
frequency_range.max = MAX_UFREQ;
|
||||
}
|
||||
if (frequency_range.max < (frequency_range.min + freqman_entry_get_step_value(def_step))) {
|
||||
frequency_range.min = frequency_range.max - freqman_entry_get_step_value(def_step);
|
||||
if (frequency_range.max < (freqman_entry_get_step_value(def_step) + 1)) {
|
||||
if (frequency_range.max < (frequency_range.min + step_val)) {
|
||||
frequency_range.min = frequency_range.max - step_val;
|
||||
if (frequency_range.max < (step_val + 1)) {
|
||||
frequency_range.min = 1;
|
||||
frequency_range.max = (freqman_entry_get_step_value(def_step) + 1);
|
||||
frequency_range.max = (step_val + 1);
|
||||
}
|
||||
}
|
||||
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
|
||||
@@ -607,87 +543,9 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_remove.on_select = [this](ButtonWithEncoder&) {
|
||||
// TODO: Use FreqmanDB
|
||||
if (frequency_list.size() > 0) {
|
||||
if (!manual_mode) {
|
||||
// scanner or recon (!scanner) mode
|
||||
// in both we delete index from live view, but only remove in output file in scanner_mode
|
||||
if (current_index >= (int32_t)frequency_list.size()) {
|
||||
current_index = frequency_list.size() - 1;
|
||||
}
|
||||
frequency_list.erase(frequency_list.begin() + current_index);
|
||||
if (current_index >= (int32_t)frequency_list.size()) {
|
||||
current_index = frequency_list.size() - 1;
|
||||
}
|
||||
if (frequency_list.size() > 0) {
|
||||
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
|
||||
update_description();
|
||||
}
|
||||
// also remove from output file if in scanner mode
|
||||
if (scanner_mode) {
|
||||
File freqman_file{};
|
||||
delete_file(freq_file_path);
|
||||
auto result = freqman_file.create(freq_file_path);
|
||||
if (!result.is_valid()) {
|
||||
for (size_t n = 0; n < frequency_list.size(); n++) {
|
||||
auto line = to_freqman_string(*frequency_list[n]);
|
||||
freqman_file.write_line(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (manual_mode) // only remove from output
|
||||
{
|
||||
File recon_file{};
|
||||
File tmp_recon_file{};
|
||||
std::string tmp_freq_file_path{freq_file_path + ".TMP"};
|
||||
handle_remove_current_item();
|
||||
|
||||
freqman_entry entry = current_entry();
|
||||
entry.frequency_a = freq;
|
||||
entry.frequency_b = 0;
|
||||
entry.modulation = last_entry.modulation;
|
||||
entry.bandwidth = last_entry.bandwidth;
|
||||
entry.type = freqman_type::Single;
|
||||
|
||||
auto frequency_to_add = to_freqman_string(entry);
|
||||
|
||||
delete_file(tmp_freq_file_path);
|
||||
auto result = tmp_recon_file.create(tmp_freq_file_path); // First recon if freq is already in txt
|
||||
if (!result.is_valid()) {
|
||||
bool found = false;
|
||||
result = recon_file.open(freq_file_path); // First recon if freq is already in txt
|
||||
if (!result.is_valid()) {
|
||||
char one_char[1]{}; // Read it char by char
|
||||
std::string line{}; // and put read line in here
|
||||
for (size_t pointer = 0; pointer < recon_file.size(); pointer++) {
|
||||
recon_file.seek(pointer);
|
||||
recon_file.read(one_char, 1);
|
||||
if ((int)one_char[0] > 31) { // ascii space upwards
|
||||
line += one_char[0]; // add it to the textline
|
||||
} else if (one_char[0] == '\n') { // new Line
|
||||
if (line.compare(0, frequency_to_add.size(), frequency_to_add) == 0) {
|
||||
found = true;
|
||||
} else {
|
||||
tmp_recon_file.write_line(frequency_to_add);
|
||||
}
|
||||
line.clear(); // ready for next textline
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
delete_file(freq_file_path);
|
||||
rename_file(tmp_freq_file_path, freq_file_path);
|
||||
} else {
|
||||
delete_file(tmp_freq_file_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
receiver_model.set_target_frequency(current_entry().frequency_a); // retune
|
||||
}
|
||||
if (frequency_list.size() == 0) {
|
||||
text_cycle.set_text(" ");
|
||||
desc_cycle.set("no entries in list"); // Show new description
|
||||
delete_file(freq_file_path);
|
||||
}
|
||||
// TODO: refresh UI.
|
||||
timer = 0;
|
||||
freq_lock = 0;
|
||||
};
|
||||
@@ -766,9 +624,8 @@ ReconView::ReconView(NavigationView& nav)
|
||||
};
|
||||
|
||||
button_restart.on_select = [this](Button&) {
|
||||
frequency_file_load(true);
|
||||
if (frequency_list.size() > 0) {
|
||||
def_step = step_mode.selected_index(); // Use def_step from manual selector
|
||||
frequency_file_load(true);
|
||||
if (fwd) {
|
||||
button_dir.set_text("FW>");
|
||||
} else {
|
||||
@@ -921,8 +778,7 @@ ReconView::ReconView(NavigationView& nav)
|
||||
recon_redraw();
|
||||
}
|
||||
|
||||
void ReconView::frequency_file_load(bool stop_all_before) {
|
||||
(void)(stop_all_before);
|
||||
void ReconView::frequency_file_load(bool) {
|
||||
if (field_mode.selected_index_value() != SPEC_MODULATION)
|
||||
audio::output::stop();
|
||||
|
||||
@@ -940,6 +796,7 @@ void ReconView::frequency_file_load(bool stop_all_before) {
|
||||
desc_cycle.set_style(&Styles::blue);
|
||||
button_scanner_mode.set_text("RECON");
|
||||
}
|
||||
|
||||
freqman_load_options options{
|
||||
.load_freqs = load_freqs,
|
||||
.load_ranges = load_ranges,
|
||||
@@ -1430,4 +1287,35 @@ void ReconView::handle_coded_squelch(const uint32_t value) {
|
||||
text_ctcss.set(" ");
|
||||
}
|
||||
|
||||
void ReconView::handle_remove_current_item() {
|
||||
if (frequency_list.empty() || !current_is_valid())
|
||||
return;
|
||||
|
||||
auto entry = current_entry(); // Copy the current entry.
|
||||
|
||||
// In Scanner or Recon modes, remove from the in-memory list.
|
||||
if (mode() != recon_mode::Manual) {
|
||||
if (current_is_valid()) {
|
||||
frequency_list.erase(frequency_list.begin() + current_index);
|
||||
}
|
||||
|
||||
if (frequency_list.size() > 0) {
|
||||
current_index = clip<int32_t>(current_index, 0u, frequency_list.size() - 1);
|
||||
text_cycle.set_text(to_string_dec_uint(current_index + 1, 3));
|
||||
update_description();
|
||||
} else {
|
||||
current_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// In Scanner or Manual mode, remove the entry from the output file.
|
||||
if (mode() != recon_mode::Recon) {
|
||||
FreqmanDB freq_db;
|
||||
if (!freq_db.open(freq_file_path))
|
||||
return;
|
||||
|
||||
freq_db.delete_entry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -46,6 +46,12 @@ namespace ui {
|
||||
|
||||
#define RECON_CFG_FILE "SETTINGS/recon.cfg"
|
||||
|
||||
enum class recon_mode : uint8_t {
|
||||
Recon,
|
||||
Scanner,
|
||||
Manual
|
||||
};
|
||||
|
||||
class ReconView : public View {
|
||||
public:
|
||||
ReconView(NavigationView& nav);
|
||||
@@ -82,9 +88,10 @@ class ReconView : public View {
|
||||
void recon_redraw();
|
||||
void handle_retune();
|
||||
void handle_coded_squelch(const uint32_t value);
|
||||
void handle_remove_current_item();
|
||||
bool recon_load_config_from_sd();
|
||||
bool recon_save_config_to_sd();
|
||||
bool recon_save_freq(const std::string& freq_file_path, size_t index, bool warn_if_exists);
|
||||
bool recon_save_freq(const std::filesystem::path& path, size_t index, bool warn_if_exists);
|
||||
// placeholder for possible void recon_start_recording();
|
||||
void recon_stop_recording();
|
||||
|
||||
@@ -92,8 +99,15 @@ class ReconView : public View {
|
||||
bool current_is_valid();
|
||||
freqman_entry& current_entry();
|
||||
|
||||
// TODO: consolidate mode bools and use recon_mode.
|
||||
recon_mode mode() const {
|
||||
if (manual_mode) return recon_mode::Manual;
|
||||
if (scanner_mode) return recon_mode::Scanner;
|
||||
return recon_mode::Recon;
|
||||
}
|
||||
|
||||
jammer::jammer_range_t frequency_range{false, 0, MAX_UFREQ}; // perfect for manual recon task too...
|
||||
int32_t squelch{0};
|
||||
int32_t squelch{RECON_DEF_SQUELCH};
|
||||
int32_t db{0};
|
||||
int32_t timer{0};
|
||||
int32_t wait{RECON_DEF_WAIT_DURATION}; // in msec. if > 0 wait duration after a lock, if < 0 duration is set to 'wait' unless there is no more activity
|
||||
@@ -117,7 +131,7 @@ class ReconView : public View {
|
||||
bool user_pause{false};
|
||||
bool auto_record_locked{false};
|
||||
bool is_recording{false};
|
||||
uint32_t recon_lock_nb_match{3};
|
||||
uint32_t recon_lock_nb_match{RECON_DEF_NB_MATCH};
|
||||
uint32_t recon_lock_duration{RECON_MIN_LOCK_DURATION};
|
||||
uint32_t recon_match_mode{RECON_MATCH_CONTINUOUS};
|
||||
bool scanner_mode{false};
|
||||
|
||||
@@ -70,11 +70,9 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
|
||||
button_save_freqs.on_select = [this, &nav](Button&) {
|
||||
auto open_view = nav.push<FileLoadView>(".TXT");
|
||||
open_view->on_changed = [this, &nav](std::filesystem::path new_file_path) {
|
||||
std::string dir_filter = "FREQMAN/";
|
||||
std::string str_file_path = new_file_path.string();
|
||||
if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder
|
||||
if (new_file_path.native().find(freqman_dir.native()) == 0) {
|
||||
_output_file = new_file_path.stem().string();
|
||||
button_output_file.set_text(_output_file);
|
||||
text_input_file.set(_output_file);
|
||||
} else {
|
||||
nav.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired.");
|
||||
}
|
||||
@@ -84,7 +82,7 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st
|
||||
button_output_file.on_select = [this, &nav](Button&) {
|
||||
text_prompt(nav, _output_file, 28,
|
||||
[this](std::string& buffer) {
|
||||
_output_file = buffer;
|
||||
_output_file = std::move(buffer);
|
||||
button_output_file.set_text(_output_file);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
// maximum lock duration
|
||||
#define RECON_MAX_LOCK_DURATION 9900
|
||||
|
||||
#define RECON_DEF_SQUELCH -14
|
||||
|
||||
// default number of match to have a lock
|
||||
#define RECON_DEF_NB_MATCH 3
|
||||
#define RECON_MIN_LOCK_DURATION 100 // have to be >= and a multiple of STATS_UPDATE_INTERVAL
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "ui_scanner.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
|
||||
@@ -480,10 +480,12 @@ class SetEncoderDialView : public View {
|
||||
|
||||
OptionsField field_encoder_dial_sensitivity{
|
||||
{20 * 8, 3 * 16},
|
||||
6,
|
||||
{{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
|
||||
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_MEDIUM},
|
||||
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH}}};
|
||||
7,
|
||||
{{"LOWEST", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOWEST},
|
||||
{"LOW", encoder_dial_sensitivity::DIAL_SENSITIVITY_LOW},
|
||||
{"NORMAL", encoder_dial_sensitivity::DIAL_SENSITIVITY_NORMAL},
|
||||
{"HIGH", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGH},
|
||||
{"HIGHEST", encoder_dial_sensitivity::DIAL_SENSITIVITY_HIGHEST}}};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
|
||||
@@ -20,13 +20,14 @@
|
||||
*/
|
||||
|
||||
#include "ui_spectrum_painter.hpp"
|
||||
|
||||
#include "bmp.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "ui_fileman.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "file.hpp"
|
||||
#include "io_file.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "ui_fileman.hpp"
|
||||
#include "ui_freqman.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
|
||||
@@ -301,6 +301,38 @@ void TextEditorMenu::hide_children(bool hidden) {
|
||||
|
||||
/* TextEditorView ***************************************************/
|
||||
|
||||
static fs::path get_temp_path(const fs::path& path) {
|
||||
if (!path.empty())
|
||||
return path + "~";
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static void delete_temp_file(const fs::path& path) {
|
||||
auto temp_path = get_temp_path(path);
|
||||
if (!temp_path.empty()) {
|
||||
delete_file(temp_path);
|
||||
}
|
||||
}
|
||||
|
||||
static void save_temp_file(const fs::path& path) {
|
||||
delete_file(path);
|
||||
copy_file(get_temp_path(path), path);
|
||||
}
|
||||
|
||||
static void show_save_prompt(
|
||||
NavigationView& nav,
|
||||
std::function<void()> on_save,
|
||||
std::function<void()> continuation) {
|
||||
nav.display_modal(
|
||||
"Save?", "Save changes?", YESNO,
|
||||
[on_save](bool choice) {
|
||||
if (choice && on_save)
|
||||
on_save();
|
||||
});
|
||||
nav.set_on_pop(continuation);
|
||||
}
|
||||
|
||||
TextEditorView::TextEditorView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children(
|
||||
@@ -381,9 +413,7 @@ TextEditorView::TextEditorView(NavigationView& nav)
|
||||
};
|
||||
|
||||
menu.on_exit() = [this]() {
|
||||
show_save_prompt([this]() {
|
||||
nav_.pop();
|
||||
});
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
button_menu.on_select = [this]() {
|
||||
@@ -402,7 +432,15 @@ TextEditorView::TextEditorView(NavigationView& nav, const fs::path& path)
|
||||
}
|
||||
|
||||
TextEditorView::~TextEditorView() {
|
||||
delete_temp_file();
|
||||
// NB: Be careful here. The UI will render after this instance
|
||||
// has been destroyed. Everything needed to render the UI
|
||||
// and perform the save actions must be value captured.
|
||||
if (file_dirty_) {
|
||||
ui::show_save_prompt(
|
||||
nav_,
|
||||
[p = path_]() { ui::save_temp_file(p); },
|
||||
[p = std::move(path_)]() { delete_temp_file(p); });
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditorView::on_show() {
|
||||
@@ -415,7 +453,7 @@ void TextEditorView::on_show() {
|
||||
void TextEditorView::open_file(const fs::path& path) {
|
||||
file_.reset();
|
||||
viewer.clear_file();
|
||||
delete_temp_file();
|
||||
delete_temp_file(path_);
|
||||
|
||||
path_ = {};
|
||||
file_dirty_ = false;
|
||||
@@ -516,13 +554,10 @@ void TextEditorView::show_save_prompt(std::function<void()> continuation) {
|
||||
return;
|
||||
}
|
||||
|
||||
nav_.display_modal(
|
||||
"Save?", "Save changes?", YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice)
|
||||
save_temp_file();
|
||||
});
|
||||
nav_.set_on_pop(continuation);
|
||||
ui::show_save_prompt(
|
||||
nav_,
|
||||
[this]() { save_temp_file(); },
|
||||
continuation);
|
||||
}
|
||||
|
||||
void TextEditorView::prepare_for_write() {
|
||||
@@ -533,31 +568,16 @@ void TextEditorView::prepare_for_write() {
|
||||
|
||||
// Copy to temp file on write.
|
||||
has_temp_file_ = true;
|
||||
delete_temp_file();
|
||||
copy_file(path_, get_temp_path());
|
||||
file_->assume_file(get_temp_path());
|
||||
}
|
||||
|
||||
void TextEditorView::delete_temp_file() const {
|
||||
auto temp_path = get_temp_path();
|
||||
if (!temp_path.empty()) {
|
||||
delete_file(temp_path);
|
||||
}
|
||||
delete_temp_file(path_);
|
||||
copy_file(path_, get_temp_path(path_));
|
||||
file_->assume_file(get_temp_path(path_));
|
||||
}
|
||||
|
||||
void TextEditorView::save_temp_file() {
|
||||
if (file_dirty_) {
|
||||
delete_file(path_);
|
||||
copy_file(get_temp_path(), path_);
|
||||
ui::save_temp_file(path_);
|
||||
file_dirty_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
fs::path TextEditorView::get_temp_path() const {
|
||||
if (!path_.empty())
|
||||
return path_ + "~";
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -229,10 +229,7 @@ class TextEditorView : public View {
|
||||
void show_save_prompt(std::function<void()> continuation);
|
||||
|
||||
void prepare_for_write();
|
||||
void create_temp_file() const;
|
||||
void delete_temp_file() const;
|
||||
void save_temp_file();
|
||||
std::filesystem::path get_temp_path() const;
|
||||
|
||||
NavigationView& nav_;
|
||||
std::unique_ptr<FileWrapper> file_{};
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Kyle Reed
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __FIELD_BINDER_H__
|
||||
#define __FIELD_BINDER_H__
|
||||
|
||||
/* There's a common pattern that appears in applications that use
|
||||
* field widgets. The field value is set, on_change and on_select
|
||||
* handlers are registered. on_select will read the value and
|
||||
* set the new value once changed. on_change will be called which
|
||||
* will write the new value to the backing variable. */
|
||||
|
||||
struct binding_traits {
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
constexpr auto get_traits() {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename TField, typename TTraits = get_traits<TField>()>
|
||||
class FieldBinding {
|
||||
};
|
||||
|
||||
#endif /*__FIELD_BINDER_H__*/
|
||||
@@ -45,8 +45,11 @@ Optional<File::Error> File::open_fatfs(const std::filesystem::path& filename, BY
|
||||
}
|
||||
}
|
||||
|
||||
Optional<File::Error> File::open(const std::filesystem::path& filename, bool read_only) {
|
||||
Optional<File::Error> File::open(const std::filesystem::path& filename, bool read_only, bool create) {
|
||||
BYTE mode = read_only ? FA_READ : FA_READ | FA_WRITE;
|
||||
if (create)
|
||||
mode |= FA_OPEN_ALWAYS;
|
||||
|
||||
return open_fatfs(filename, mode);
|
||||
}
|
||||
|
||||
|
||||
@@ -319,7 +319,7 @@ class File {
|
||||
File& operator=(const File&) = delete;
|
||||
|
||||
// TODO: Return Result<>.
|
||||
Optional<Error> open(const std::filesystem::path& filename, bool read_only = true);
|
||||
Optional<Error> open(const std::filesystem::path& filename, bool read_only = true, bool create = false);
|
||||
Optional<Error> append(const std::filesystem::path& filename);
|
||||
Optional<Error> create(const std::filesystem::path& filename);
|
||||
|
||||
|
||||
@@ -463,9 +463,9 @@ class FileWrapper : public BufferWrapper<File, 64> {
|
||||
template <typename T>
|
||||
using Result = File::Result<T>;
|
||||
using Error = File::Error;
|
||||
static Result<std::unique_ptr<FileWrapper>> open(const std::filesystem::path& path) {
|
||||
static Result<std::unique_ptr<FileWrapper>> open(const std::filesystem::path& path, bool create = false) {
|
||||
auto fw = std::unique_ptr<FileWrapper>(new FileWrapper());
|
||||
auto error = fw->file_.open(path, /*read_only*/ false);
|
||||
auto error = fw->file_.open(path, /*read_only*/ false, create);
|
||||
|
||||
if (error)
|
||||
return *error;
|
||||
|
||||
@@ -22,92 +22,16 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "file.hpp"
|
||||
#include "freqman.hpp"
|
||||
#include "tone_key.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
using option_t = ui::OptionsField::option_t;
|
||||
using options_t = ui::OptionsField::options_t;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using namespace tonekey;
|
||||
using namespace ui;
|
||||
|
||||
using option_t = std::pair<std::string, int32_t>;
|
||||
using options_t = std::vector<option_t>;
|
||||
|
||||
extern options_t freqman_modulations;
|
||||
extern options_t freqman_bandwidths[4];
|
||||
extern options_t freqman_steps;
|
||||
extern options_t freqman_steps_short;
|
||||
|
||||
extern const option_t* find_by_index(const options_t& options, freqman_index_t index);
|
||||
|
||||
// TODO: remove in favor of FreqmanDB
|
||||
/* Freqman file handling. */
|
||||
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options) {
|
||||
return parse_freqman_file(get_freqman_path(file_stem), db, options);
|
||||
}
|
||||
|
||||
bool delete_freqman_file(const std::string& file_stem) {
|
||||
delete_file(get_freqman_path(file_stem));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool save_freqman_file(const std::string& file_stem, freqman_db& db) {
|
||||
auto path = get_freqman_path(file_stem);
|
||||
delete_file(path);
|
||||
|
||||
File freqman_file;
|
||||
auto error = freqman_file.create(path);
|
||||
if (error)
|
||||
return false;
|
||||
|
||||
for (size_t n = 0; n < db.size(); n++)
|
||||
freqman_file.write_line(to_freqman_string(*db[n]));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool create_freqman_file(const std::string& file_stem) {
|
||||
auto fs_error = make_new_file(get_freqman_path(file_stem));
|
||||
return fs_error.ok();
|
||||
}
|
||||
|
||||
/* Set options. */
|
||||
void freqman_set_modulation_option(OptionsField& option) {
|
||||
option.set_options(freqman_modulations);
|
||||
}
|
||||
|
||||
void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) {
|
||||
if (is_valid(modulation))
|
||||
option.set_options(freqman_bandwidths[modulation]);
|
||||
}
|
||||
|
||||
void freqman_set_step_option(OptionsField& option) {
|
||||
option.set_options(freqman_steps);
|
||||
}
|
||||
|
||||
void freqman_set_step_option_short(OptionsField& option) {
|
||||
option.set_options(freqman_steps_short);
|
||||
}
|
||||
|
||||
/* Option value lookup. */
|
||||
// TODO: use Optional instead of magic values.
|
||||
int32_t freqman_entry_get_modulation_value(freqman_index_t modulation) {
|
||||
if (auto opt = find_by_index(freqman_modulations, modulation))
|
||||
return opt->second;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t freqman_entry_get_bandwidth_value(freqman_index_t modulation, freqman_index_t bandwidth) {
|
||||
if (modulation < freqman_modulations.size()) {
|
||||
if (auto opt = find_by_index(freqman_bandwidths[modulation], bandwidth))
|
||||
return opt->second;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t freqman_entry_get_step_value(freqman_index_t step) {
|
||||
if (auto opt = find_by_index(freqman_steps, step))
|
||||
return opt->second;
|
||||
|
||||
@@ -25,13 +25,7 @@
|
||||
#ifndef __FREQMAN_H__
|
||||
#define __FREQMAN_H__
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include "file.hpp"
|
||||
#include "freqman_db.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
// Defined for back-compat.
|
||||
#define FREQMAN_MAX_PER_FILE freqman_default_max_entries
|
||||
@@ -50,21 +44,7 @@ enum freqman_entry_modulation : uint8_t {
|
||||
SPEC_MODULATION
|
||||
};
|
||||
|
||||
// TODO: Replace with FreqmanDB.
|
||||
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options);
|
||||
bool delete_freqman_file(const std::string& file_stem);
|
||||
bool save_freqman_file(const std::string& file_stem, freqman_db& db);
|
||||
bool create_freqman_file(const std::string& file_stem);
|
||||
|
||||
void freqman_set_bandwidth_option(freqman_index_t modulation, ui::OptionsField& option);
|
||||
void freqman_set_modulation_option(ui::OptionsField& option);
|
||||
void freqman_set_step_option(ui::OptionsField& option);
|
||||
void freqman_set_step_option_short(ui::OptionsField& option);
|
||||
void freqman_set_tone_option(ui::OptionsField& option);
|
||||
|
||||
// TODO: Can these be removed after Recon is migrated to FreqmanDB?
|
||||
int32_t freqman_entry_get_modulation_value(freqman_index_t modulation);
|
||||
int32_t freqman_entry_get_bandwidth_value(freqman_index_t modulation, freqman_index_t bandwidth);
|
||||
int32_t freqman_entry_get_step_value(freqman_index_t step);
|
||||
|
||||
#endif /*__FREQMAN_H__*/
|
||||
|
||||
@@ -40,10 +40,6 @@ namespace fs = std::filesystem;
|
||||
const std::filesystem::path freqman_dir{u"/FREQMAN"};
|
||||
const std::filesystem::path freqman_extension{u".TXT"};
|
||||
|
||||
const std::filesystem::path get_freqman_path(const std::string& stem) {
|
||||
return freqman_dir / stem + freqman_extension;
|
||||
}
|
||||
|
||||
// NB: Don't include UI headers to keep this code unit testable.
|
||||
using option_t = std::pair<std::string, int32_t>;
|
||||
using options_t = std::vector<option_t>;
|
||||
@@ -97,6 +93,7 @@ options_t freqman_bandwidths[4] = {
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: these should be indexes.
|
||||
options_t freqman_steps = {
|
||||
{"0.1kHz ", 100},
|
||||
{"1kHz ", 1000},
|
||||
@@ -116,6 +113,7 @@ options_t freqman_steps = {
|
||||
{"1MHz ", 1000000},
|
||||
};
|
||||
|
||||
// TODO: these should be indexes.
|
||||
options_t freqman_steps_short = {
|
||||
{"0.1kHz", 100},
|
||||
{"1kHz", 1000},
|
||||
@@ -162,6 +160,27 @@ const option_t* find_by_index(const options_t& options, freqman_index_t index) {
|
||||
*}
|
||||
*/
|
||||
|
||||
bool operator==(const freqman_entry& lhs, const freqman_entry& rhs) {
|
||||
auto equal = lhs.type == rhs.type &&
|
||||
lhs.frequency_a == rhs.frequency_a &&
|
||||
lhs.description == rhs.description &&
|
||||
lhs.modulation == rhs.modulation &&
|
||||
lhs.bandwidth == rhs.bandwidth;
|
||||
|
||||
if (!equal)
|
||||
return false;
|
||||
|
||||
if (lhs.type == freqman_type::Range) {
|
||||
equal = lhs.frequency_b == rhs.frequency_b &&
|
||||
lhs.step == rhs.step;
|
||||
} else if (lhs.type == freqman_type::HamRadio) {
|
||||
equal = lhs.frequency_b == rhs.frequency_b &&
|
||||
lhs.tone == rhs.tone;
|
||||
}
|
||||
|
||||
return equal;
|
||||
}
|
||||
|
||||
std::string freqman_entry_get_modulation_string(freqman_index_t modulation) {
|
||||
if (auto opt = find_by_index(freqman_modulations, modulation))
|
||||
return opt->first;
|
||||
@@ -188,6 +207,23 @@ std::string freqman_entry_get_step_string_short(freqman_index_t step) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::filesystem::path get_freqman_path(const std::string& stem) {
|
||||
return freqman_dir / stem + freqman_extension;
|
||||
}
|
||||
|
||||
bool create_freqman_file(const std::string& file_stem) {
|
||||
auto fs_error = make_new_file(get_freqman_path(file_stem));
|
||||
return fs_error.ok();
|
||||
}
|
||||
|
||||
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options) {
|
||||
return parse_freqman_file(get_freqman_path(file_stem), db, options);
|
||||
}
|
||||
|
||||
void delete_freqman_file(const std::string& file_stem) {
|
||||
delete_file(get_freqman_path(file_stem));
|
||||
}
|
||||
|
||||
std::string pretty_string(const freqman_entry& entry, size_t max_length) {
|
||||
std::string str;
|
||||
|
||||
@@ -196,12 +232,15 @@ std::string pretty_string(const freqman_entry& entry, size_t max_length) {
|
||||
str = to_string_short_freq(entry.frequency_a) + "M: " + entry.description;
|
||||
break;
|
||||
case freqman_type::Range:
|
||||
str = to_string_dec_uint(entry.frequency_a / 1'000'000) + "M-" +
|
||||
to_string_dec_uint(entry.frequency_b / 1'000'000) + "M: " + entry.description;
|
||||
str = to_string_rounded_freq(entry.frequency_a, 1) + "M-" +
|
||||
to_string_rounded_freq(entry.frequency_b, 1) + "M: " + entry.description;
|
||||
break;
|
||||
case freqman_type::HamRadio:
|
||||
str = "" + to_string_dec_uint(entry.frequency_a / 1'000'000) + "M," +
|
||||
to_string_dec_uint(entry.frequency_b / 1'000'000) + "M: " + entry.description;
|
||||
str = "R:" + to_string_rounded_freq(entry.frequency_a, 1) + "M,T:" +
|
||||
to_string_rounded_freq(entry.frequency_b, 1) + "M: " + entry.description;
|
||||
break;
|
||||
case freqman_type::Raw:
|
||||
str = entry.description;
|
||||
break;
|
||||
default:
|
||||
str = "UNK:" + entry.description;
|
||||
@@ -244,8 +283,10 @@ std::string to_freqman_string(const freqman_entry& entry) {
|
||||
if (is_valid(entry.tone))
|
||||
append_field("c", tonekey::tone_key_value_string(entry.tone));
|
||||
break;
|
||||
case freqman_type::Raw:
|
||||
return entry.description;
|
||||
default:
|
||||
return {}; // TODO: Comment type with description?
|
||||
return {};
|
||||
};
|
||||
|
||||
if (is_valid(entry.modulation) && entry.modulation < freqman_modulations.size()) {
|
||||
@@ -332,48 +373,23 @@ bool parse_freqman_entry(std::string_view str, freqman_entry& entry) {
|
||||
}
|
||||
}
|
||||
|
||||
// No valid frequency combination was set.
|
||||
if (entry.type == freqman_type::Unknown)
|
||||
return false;
|
||||
|
||||
// Ranges should have both frequencies set and A <= B.
|
||||
if (entry.type == freqman_type::Range || entry.type == freqman_type::HamRadio) {
|
||||
if (entry.frequency_a == 0 || entry.frequency_b == 0)
|
||||
return false;
|
||||
|
||||
if (entry.frequency_a > entry.frequency_b)
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Consider additional validation:
|
||||
// - Tone only on HamRadio.
|
||||
// - Fail on failed parse_int.
|
||||
// - Fail if bandwidth set before modulation.
|
||||
|
||||
return true;
|
||||
return is_valid(entry);
|
||||
}
|
||||
|
||||
// TODO: Use FreqmanDB iterator.
|
||||
bool parse_freqman_file(const fs::path& path, freqman_db& db, freqman_load_options options) {
|
||||
File f;
|
||||
auto error = f.open(path);
|
||||
if (error)
|
||||
FreqmanDB freqman_db;
|
||||
freqman_db.set_read_raw(false); // Don't return malformed lines.
|
||||
if (!freqman_db.open(path))
|
||||
return false;
|
||||
|
||||
auto reader = FileLineReader(f);
|
||||
auto line_count = count_lines(reader);
|
||||
|
||||
// Attempt to avoid a re-alloc if possible.
|
||||
db.clear();
|
||||
db.reserve(line_count);
|
||||
|
||||
for (const auto& line : reader) {
|
||||
freqman_entry entry{};
|
||||
if (!parse_freqman_entry(line, entry))
|
||||
continue;
|
||||
db.reserve(freqman_db.entry_count());
|
||||
|
||||
for (auto entry : freqman_db) {
|
||||
// Filter by entry type.
|
||||
if ((entry.type == freqman_type::Single && !options.load_freqs) ||
|
||||
if (entry.type == freqman_type::Unknown ||
|
||||
(entry.type == freqman_type::Single && !options.load_freqs) ||
|
||||
(entry.type == freqman_type::Range && !options.load_ranges) ||
|
||||
(entry.type == freqman_type::HamRadio && !options.load_hamradios)) {
|
||||
continue;
|
||||
@@ -399,10 +415,40 @@ bool parse_freqman_file(const fs::path& path, freqman_db& db, freqman_load_optio
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_valid(const freqman_entry& entry) {
|
||||
// No valid frequency combination was set.
|
||||
if (entry.type == freqman_type::Unknown)
|
||||
return false;
|
||||
|
||||
// Frequency A must be set for all types
|
||||
if (entry.frequency_a == 0)
|
||||
return false;
|
||||
|
||||
// Frequency B must be set for type Range or Ham Radio
|
||||
if (entry.type == freqman_type::Range || entry.type == freqman_type::HamRadio) {
|
||||
if (entry.frequency_b == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ranges should have frequencies A <= B.
|
||||
if (entry.type == freqman_type::Range) {
|
||||
if (entry.frequency_a > entry.frequency_b)
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Consider additional validation:
|
||||
// - Tone only on HamRadio.
|
||||
// - Step only on Range
|
||||
// - Fail on failed parse_int.
|
||||
// - Fail if bandwidth set before modulation.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* FreqmanDB ***********************************/
|
||||
|
||||
bool FreqmanDB::open(const std::filesystem::path& path) {
|
||||
auto result = FileWrapper::open(path);
|
||||
bool FreqmanDB::open(const std::filesystem::path& path, bool create) {
|
||||
auto result = FileWrapper::open(path, create);
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
@@ -414,38 +460,61 @@ void FreqmanDB::close() {
|
||||
wrapper_.reset();
|
||||
}
|
||||
|
||||
freqman_entry FreqmanDB::operator[](FileWrapper::Line line) const {
|
||||
auto length = wrapper_->line_length(line);
|
||||
auto line_text = wrapper_->get_text(line, 0, length);
|
||||
freqman_entry FreqmanDB::operator[](Index index) const {
|
||||
auto length = wrapper_->line_length(index);
|
||||
auto line_text = wrapper_->get_text(index, 0, length);
|
||||
|
||||
if (line_text) {
|
||||
freqman_entry entry;
|
||||
if (parse_freqman_entry(*line_text, entry))
|
||||
return entry;
|
||||
else if (read_raw_) {
|
||||
entry.type = freqman_type::Raw;
|
||||
entry.description = trim(*line_text);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void FreqmanDB::insert_entry(const freqman_entry& entry, FileWrapper::Line line) {
|
||||
// TODO: Can be more efficient.
|
||||
line = clip<uint32_t>(line, 0u, entry_count());
|
||||
wrapper_->insert_line(line);
|
||||
replace_entry(line, entry);
|
||||
void FreqmanDB::insert_entry(Index index, const freqman_entry& entry) {
|
||||
index = clip<uint32_t>(index, 0u, entry_count());
|
||||
wrapper_->insert_line(index);
|
||||
replace_entry(index, entry);
|
||||
}
|
||||
|
||||
void FreqmanDB::replace_entry(FileWrapper::Line line, const freqman_entry& entry) {
|
||||
auto range = wrapper_->line_range(line);
|
||||
void FreqmanDB::append_entry(const freqman_entry& entry) {
|
||||
insert_entry(entry_count(), entry);
|
||||
}
|
||||
|
||||
void FreqmanDB::replace_entry(Index index, const freqman_entry& entry) {
|
||||
auto range = wrapper_->line_range(index);
|
||||
if (!range)
|
||||
return; // TODO: Message?
|
||||
return;
|
||||
|
||||
// Don't overwrite the '\n'.
|
||||
range->end--;
|
||||
wrapper_->replace_range(*range, to_freqman_string(entry));
|
||||
}
|
||||
|
||||
void FreqmanDB::delete_entry(FileWrapper::Line line) {
|
||||
wrapper_->delete_line(line);
|
||||
void FreqmanDB::delete_entry(Index index) {
|
||||
wrapper_->delete_line(index);
|
||||
}
|
||||
|
||||
bool FreqmanDB::delete_entry(const freqman_entry& entry) {
|
||||
auto it = find_entry(entry);
|
||||
if (it == end())
|
||||
return false;
|
||||
|
||||
delete_entry(it.index());
|
||||
return true;
|
||||
}
|
||||
|
||||
FreqmanDB::iterator FreqmanDB::find_entry(const freqman_entry& entry) {
|
||||
return find_entry([&entry](const auto& other) {
|
||||
return entry == other;
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t FreqmanDB::entry_count() const {
|
||||
@@ -457,4 +526,4 @@ bool FreqmanDB::empty() const {
|
||||
// FileWrapper always presents a single line even for empty files.
|
||||
// A DB is only really empty if the file size is 0.
|
||||
return !wrapper_ || wrapper_->size() == 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,13 +52,11 @@ constexpr bool is_invalid(freqman_index_t index) {
|
||||
return index == freqman_invalid_index;
|
||||
}
|
||||
|
||||
/* Gets the full path for a given file stem (no extension). */
|
||||
const std::filesystem::path get_freqman_path(const std::string& stem);
|
||||
|
||||
enum class freqman_type : uint8_t {
|
||||
Single, // f=
|
||||
Range, // a=,b=
|
||||
HamRadio, // r=,t=
|
||||
Raw, // line content in description
|
||||
Unknown,
|
||||
};
|
||||
|
||||
@@ -140,9 +138,9 @@ enum class freqman_type : uint8_t {
|
||||
|
||||
/* Freqman Entry *******************************/
|
||||
struct freqman_entry {
|
||||
int64_t frequency_a{0}; // 'f=freq' or 'a=freq_start' or 'r=recv_freq'
|
||||
int64_t frequency_b{0}; // 'b=freq_end' or 't=tx_freq'
|
||||
std::string description{0}; // 'd=desc'
|
||||
int64_t frequency_a{0}; // 'f=freq' or 'a=freq_start' or 'r=recv_freq'
|
||||
int64_t frequency_b{0}; // 'b=freq_end' or 't=tx_freq'
|
||||
std::string description{}; // 'd=desc'
|
||||
freqman_type type{freqman_type::Unknown};
|
||||
freqman_index_t modulation{freqman_invalid_index};
|
||||
freqman_index_t bandwidth{freqman_invalid_index};
|
||||
@@ -150,6 +148,8 @@ struct freqman_entry {
|
||||
freqman_index_t tone{freqman_invalid_index};
|
||||
};
|
||||
|
||||
bool operator==(const freqman_entry& lhs, const freqman_entry& rhs);
|
||||
|
||||
// TODO: These shouldn't be exported.
|
||||
std::string freqman_entry_get_modulation_string(freqman_index_t modulation);
|
||||
std::string freqman_entry_get_bandwidth_string(freqman_index_t modulation, freqman_index_t bandwidth);
|
||||
@@ -172,6 +172,12 @@ struct freqman_load_options {
|
||||
using freqman_entry_ptr = std::unique_ptr<freqman_entry>;
|
||||
using freqman_db = std::vector<freqman_entry_ptr>;
|
||||
|
||||
/* Gets the full path for a given file stem (no extension). */
|
||||
const std::filesystem::path get_freqman_path(const std::string& stem);
|
||||
bool create_freqman_file(const std::string& file_stem);
|
||||
bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options);
|
||||
void delete_freqman_file(const std::string& file_stem);
|
||||
|
||||
/* Gets a pretty string representation for an entry. */
|
||||
std::string pretty_string(const freqman_entry& item, size_t max_length = 30);
|
||||
|
||||
@@ -181,54 +187,94 @@ std::string to_freqman_string(const freqman_entry& entry);
|
||||
bool parse_freqman_entry(std::string_view str, freqman_entry& entry);
|
||||
bool parse_freqman_file(const std::filesystem::path& path, freqman_db& db, freqman_load_options options);
|
||||
|
||||
/* The tricky part of using the file directly is that there can be comments
|
||||
* and empty lines in the file. This messes up the 'count' calculation.
|
||||
* Either have to live with 'count' being an upper bound have the callers
|
||||
* know to expect that entries may be empty. */
|
||||
// NB: This won't apply implicit mod/bandwidth.
|
||||
// TODO: Reuse for parse_freqman_file
|
||||
/* Returns true if the entry is well-formed. */
|
||||
bool is_valid(const freqman_entry& entry);
|
||||
|
||||
/* API wrapper over a Freqman file. Provides CRUD operations
|
||||
* for freqman_entry instances that are read/written directly
|
||||
* to the underlying file. */
|
||||
class FreqmanDB {
|
||||
public:
|
||||
using Index = FileWrapper::Line;
|
||||
|
||||
/* NB: This iterator is very basic: forward only, read-only. */
|
||||
class iterator {
|
||||
public:
|
||||
iterator(FreqmanDB& db, FileWrapper::Offset line)
|
||||
: db_{db}, line_{line} {}
|
||||
iterator(FreqmanDB& db, Index index)
|
||||
: db_{db}, index_{index} {}
|
||||
iterator& operator++() {
|
||||
line_++;
|
||||
index_++;
|
||||
|
||||
if (index_ >= db_.entry_count())
|
||||
index_ = end_index;
|
||||
|
||||
return *this;
|
||||
}
|
||||
freqman_entry operator*() const {
|
||||
return db_[line_];
|
||||
return db_[index_];
|
||||
}
|
||||
|
||||
bool operator==(const iterator& other) {
|
||||
return &db_ == &other.db_ && index_ == other.index_;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator& other) {
|
||||
return &db_ != &other.db_ || line_ != other.line_;
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
Index index() const {
|
||||
return index_;
|
||||
}
|
||||
|
||||
/* Value indicating the 'end' iterator. */
|
||||
static constexpr Index end_index = (Index)-1;
|
||||
|
||||
private:
|
||||
FreqmanDB& db_;
|
||||
FileWrapper::Line line_;
|
||||
Index index_;
|
||||
};
|
||||
|
||||
bool open(const std::filesystem::path& path);
|
||||
bool open(const std::filesystem::path& path, bool create = false);
|
||||
void close();
|
||||
freqman_entry operator[](FileWrapper::Line line) const;
|
||||
void insert_entry(const freqman_entry& entry, FileWrapper::Line line);
|
||||
void replace_entry(FileWrapper::Line line, const freqman_entry& entry);
|
||||
void delete_entry(FileWrapper::Line line);
|
||||
|
||||
freqman_entry operator[](Index index) const;
|
||||
void insert_entry(Index index, const freqman_entry& entry);
|
||||
void append_entry(const freqman_entry& entry);
|
||||
void replace_entry(Index index, const freqman_entry& entry);
|
||||
void delete_entry(Index index);
|
||||
bool delete_entry(const freqman_entry& entry);
|
||||
|
||||
template <typename Fn>
|
||||
iterator find_entry(const Fn& predicate) {
|
||||
// TODO: use std::find, but need to make the iterator compliant.
|
||||
auto it = begin();
|
||||
const auto it_end = end();
|
||||
|
||||
while (it != it_end) {
|
||||
if (predicate(*it))
|
||||
return it;
|
||||
++it;
|
||||
}
|
||||
|
||||
return it_end;
|
||||
}
|
||||
iterator find_entry(const freqman_entry& entry);
|
||||
|
||||
uint32_t entry_count() const;
|
||||
bool empty() const;
|
||||
|
||||
/* When true, Raw entries are returned instead of Unknown. */
|
||||
void set_read_raw(bool v) { read_raw_ = v; }
|
||||
iterator begin() {
|
||||
return {*this, 0};
|
||||
}
|
||||
iterator end() {
|
||||
return {*this, entry_count()};
|
||||
return {*this, iterator::end_index};
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<FileWrapper> wrapper_{};
|
||||
bool read_raw_{true};
|
||||
};
|
||||
|
||||
#endif /* __FREQMAN_DB_H__ */
|
||||
@@ -32,66 +32,24 @@
|
||||
// 12 degrees of rotation.
|
||||
//
|
||||
// For each encoder "pulse" there are 4 state transitions, and we can choose
|
||||
// between looking at all of them (high sensitivity), half of them (medium/default),
|
||||
// or one quarter of them (low sensitivity).
|
||||
static const int8_t transition_map[][16] = {
|
||||
// Normal (Medium) Sensitivity -- default
|
||||
{
|
||||
0, // 0000: noop
|
||||
0, // 0001: ccw start
|
||||
0, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
-1, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
1, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
0, // 1101: cw start
|
||||
0, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
},
|
||||
// Low Sensitivity
|
||||
{
|
||||
0, // 0000: noop
|
||||
0, // 0001: ccw start
|
||||
0, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
0, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
0, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
0, // 1101: cw start
|
||||
0, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
},
|
||||
// High Sensitivity
|
||||
{
|
||||
0, // 0000: noop
|
||||
-1, // 0001: ccw start
|
||||
1, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
-1, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
1, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
1, // 1101: cw start
|
||||
-1, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
},
|
||||
// how many transitions are needed before movement is registered
|
||||
static const int8_t transition_map[16] = {
|
||||
0, // 0000: noop
|
||||
-1, // 0001: ccw start
|
||||
1, // 0010: cw start
|
||||
0, // 0011: rate
|
||||
1, // 0100: cw end
|
||||
0, // 0101: noop
|
||||
0, // 0110: rate
|
||||
-1, // 0111: ccw end
|
||||
-1, // 1000: ccw end
|
||||
0, // 1001: rate
|
||||
0, // 1010: noop
|
||||
1, // 1011: cw end
|
||||
0, // 1100: rate
|
||||
1, // 1101: cw start
|
||||
-1, // 1110: ccw start
|
||||
0, // 1111: noop
|
||||
};
|
||||
|
||||
int_fast8_t Encoder::update(
|
||||
@@ -102,6 +60,13 @@ int_fast8_t Encoder::update(
|
||||
state <<= 1;
|
||||
state |= phase_1;
|
||||
|
||||
// dial sensitivity setting is stored in pmem
|
||||
return transition_map[portapack::persistent_memory::config_encoder_dial_sensitivity()][state & 0xf];
|
||||
int_fast8_t retval = transition_map[state & 0xf];
|
||||
|
||||
transition_count += retval;
|
||||
if (abs(transition_count) > portapack::persistent_memory::config_encoder_dial_sensitivity())
|
||||
transition_count = 0;
|
||||
else
|
||||
retval = 0;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ class Encoder {
|
||||
|
||||
private:
|
||||
uint_fast8_t state{0};
|
||||
int_fast8_t transition_count{0};
|
||||
};
|
||||
|
||||
#endif /*__ENCODER_H__*/
|
||||
|
||||
@@ -111,8 +111,8 @@ std::string to_string_dec_uint(
|
||||
auto term = p + sizeof(p) - 1;
|
||||
auto q = to_string_dec_uint_pad_internal(term, n, l, fill);
|
||||
|
||||
// TODO: is this needed, seems like pad already does this.
|
||||
// Right justify.
|
||||
// (This code is redundant and won't do anything if a fill character was specified)
|
||||
while ((term - q) < l) {
|
||||
*(--q) = ' ';
|
||||
}
|
||||
@@ -137,6 +137,7 @@ std::string to_string_dec_int(
|
||||
}
|
||||
|
||||
// Right justify.
|
||||
// (This code is redundant and won't do anything if a fill character was specified)
|
||||
while ((term - q) < l) {
|
||||
*(--q) = ' ';
|
||||
}
|
||||
@@ -161,13 +162,47 @@ std::string to_string_decimal(float decimal, int8_t precision) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// right-justified frequency in Hz, always 10 characters
|
||||
std::string to_string_freq(const uint64_t f) {
|
||||
auto final_str = to_string_dec_int(f / 1000000, 4) + to_string_dec_int(f % 1000000, 6, '0');
|
||||
std::string final_str{""};
|
||||
|
||||
if (f < 1000000)
|
||||
final_str = to_string_dec_int(f, 10, ' ');
|
||||
else
|
||||
final_str = to_string_dec_int(f / 1000000, 4) + to_string_dec_int(f % 1000000, 6, '0');
|
||||
|
||||
return final_str;
|
||||
}
|
||||
|
||||
// right-justified frequency in MHz, rounded to 4 decimal places, always 9 characters
|
||||
std::string to_string_short_freq(const uint64_t f) {
|
||||
auto final_str = to_string_dec_int(f / 1000000, 4) + "." + to_string_dec_int((f / 100) % 10000, 4, '0');
|
||||
auto final_str = to_string_dec_int(f / 1000000, 4) + "." + to_string_dec_int(((f + 50) / 100) % 10000, 4, '0');
|
||||
return final_str;
|
||||
}
|
||||
|
||||
// non-justified non-padded frequency in MHz, rounded to specified number of decimal places
|
||||
std::string to_string_rounded_freq(const uint64_t f, int8_t precision) {
|
||||
std::string final_str{""};
|
||||
static constexpr uint32_t pow10[7] = {
|
||||
1,
|
||||
10,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
};
|
||||
|
||||
if (precision < 1) {
|
||||
final_str = to_string_dec_uint64(f / 1000000);
|
||||
} else {
|
||||
if (precision > 6)
|
||||
precision = 6;
|
||||
|
||||
uint32_t divisor = pow10[6 - precision];
|
||||
|
||||
final_str = to_string_dec_uint64(f / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
|
||||
}
|
||||
return final_str;
|
||||
}
|
||||
|
||||
@@ -285,7 +320,7 @@ std::string unit_auto_scale(double n, const uint32_t base_nano, uint32_t precisi
|
||||
|
||||
string = to_string_dec_int(integer_part);
|
||||
if (precision)
|
||||
string += '.' + to_string_dec_uint(fractional_part, precision);
|
||||
string += '.' + to_string_dec_uint(fractional_part, precision, '0');
|
||||
|
||||
if (prefix_index != 3)
|
||||
string += unit_prefix[prefix_index];
|
||||
|
||||
@@ -59,9 +59,9 @@ std::string to_string_decimal(float decimal, int8_t precision);
|
||||
std::string to_string_hex(const uint64_t n, const int32_t l = 0);
|
||||
std::string to_string_hex_array(uint8_t* const array, const int32_t l = 0);
|
||||
|
||||
// NB: These pad-left and don't work correctly for values less than 1M.
|
||||
std::string to_string_freq(const uint64_t f);
|
||||
std::string to_string_short_freq(const uint64_t f);
|
||||
std::string to_string_rounded_freq(const uint64_t f, int8_t precision);
|
||||
std::string to_string_time_ms(const uint32_t ms);
|
||||
|
||||
std::string to_string_datetime(const rtc::RTC& value, const TimeFormat format = YMDHMS);
|
||||
|
||||
@@ -56,16 +56,19 @@ void FreqManUIList::paint(Painter& painter) {
|
||||
auto text = std::string{};
|
||||
auto index = start_index_ + offset;
|
||||
auto line_position = rect.location() + Point{4, 1 + (int)offset * char_height};
|
||||
|
||||
// Highlight the selected item.
|
||||
auto style = (offset == selected_index_) ? &Styles::bg_white : base_style;
|
||||
auto is_selected = offset == selected_index_;
|
||||
auto style = base_style;
|
||||
|
||||
if (index < db_->entry_count()) {
|
||||
auto entry = (*db_)[index];
|
||||
// db_ is directly backed by a file, so invalid lines cannot be
|
||||
// pre-filtered. Just show an empty 'slot' in this case.
|
||||
// pre-filtered. Show an empty entry if 'Unknown'.
|
||||
if (entry.type != freqman_type::Unknown)
|
||||
text = pretty_string(entry, line_max_length);
|
||||
|
||||
// Otherwise, if 'Raw' indicate an invalid entry by color.
|
||||
if (entry.type == freqman_type::Raw)
|
||||
style = &Styles::light_grey;
|
||||
}
|
||||
|
||||
// Pad right with ' ' so trailing chars are cleaned up.
|
||||
@@ -73,7 +76,8 @@ void FreqManUIList::paint(Painter& painter) {
|
||||
if (text.length() < line_max_length)
|
||||
text.resize(line_max_length, ' ');
|
||||
|
||||
painter.draw_string(line_position, *style, text);
|
||||
painter.draw_string(
|
||||
line_position, (is_selected ? style->invert() : *style), text);
|
||||
}
|
||||
|
||||
// Draw a bounding rectangle when focused.
|
||||
|
||||
@@ -84,8 +84,8 @@ class GeoPos : public View {
|
||||
|
||||
Labels labels_position{
|
||||
{{1 * 8, 0 * 16}, "Alt:", Color::light_grey()},
|
||||
{{1 * 8, 1 * 16}, "Lat: \x90 ' \"", Color::light_grey()}, // 0x90 is degree ° symbol in our 8x16 font
|
||||
{{1 * 8, 2 * 16}, "Lon: \x90 ' \"", Color::light_grey()},
|
||||
{{1 * 8, 1 * 16}, "Lat: \xB0 ' \"", Color::light_grey()}, // 0xB0 is degree ° symbol in our 8x16 font
|
||||
{{1 * 8, 2 * 16}, "Lon: \xB0 ' \"", Color::light_grey()},
|
||||
};
|
||||
|
||||
NumberField field_altitude{
|
||||
|
||||
@@ -48,7 +48,7 @@ class TextEntryView : public View {
|
||||
void char_add(const char c);
|
||||
void char_delete();
|
||||
|
||||
TextField text_input;
|
||||
TextEdit text_input;
|
||||
Button button_ok{
|
||||
{22 * 8, 33 * 8, 7 * 8, 32},
|
||||
"OK"};
|
||||
|
||||
@@ -42,34 +42,33 @@ ReplayProcessor::ReplayProcessor() {
|
||||
}
|
||||
|
||||
void ReplayProcessor::execute(const buffer_c8_t& buffer) {
|
||||
/* 4MHz, 2048 samples */
|
||||
/* 2.6MHz, 2048 samples */
|
||||
|
||||
if (!configured) return;
|
||||
|
||||
// File data is in C16 format, we need C8
|
||||
// File samplerate is 500kHz, we're at 4MHz
|
||||
// iq_buffer can only be 512 C16 samples (RAM limitation)
|
||||
// To fill up the 2048-sample C8 buffer, we need:
|
||||
// 2048 samples * 2 bytes per sample = 4096 bytes
|
||||
// Since we're oversampling by 4M/500k = 8, we only need 2048/8 = 256 samples from the file and duplicate them 8 times each
|
||||
// So 256 * 4 bytes per sample (C16) = 1024 bytes from the file
|
||||
if (stream) { // sizeof(*buffer.p) = sizeof(C8) = 2*int8 = 2 bytes //buffer.count = 2048
|
||||
const size_t bytes_to_read = sizeof(*buffer.p) * 1 * (buffer.count); // *2 (C16), /8 (oversampling) should be == 1024
|
||||
bytes_read += stream->read(iq_buffer.p, bytes_to_read);
|
||||
}
|
||||
// File data is in C8 format, which is what we need
|
||||
// File samplerate is 2.6MHz, which is what we need
|
||||
// To fill up the 2048-sample C8 buffer @ 2 bytes per sample = 4096 bytes
|
||||
if (stream) {
|
||||
const size_t bytes_to_read = sizeof(*buffer.p) * 1 * (buffer.count);
|
||||
size_t bytes_read_this_iteration = stream->read(iq_buffer.p, bytes_to_read);
|
||||
bytes_read += bytes_read_this_iteration;
|
||||
|
||||
// Fill and "stretch"
|
||||
for (size_t i = 0; i < buffer.count; i++) {
|
||||
auto re_out = iq_buffer.p[i].real();
|
||||
auto im_out = iq_buffer.p[i].imag();
|
||||
buffer.p[i] = {(int8_t)re_out, (int8_t)im_out};
|
||||
// NB: Couldn't we have just read the data into buffer.p to start with, or some DMA/cache coherency concern?
|
||||
//
|
||||
// for (size_t i = 0; i < buffer.count; i++) {
|
||||
// auto re_out = iq_buffer.p[i].real();
|
||||
// auto im_out = iq_buffer.p[i].imag();
|
||||
// buffer.p[i] = {(int8_t)re_out, (int8_t)im_out};
|
||||
// }
|
||||
memcpy(buffer.p, iq_buffer.p, bytes_read_this_iteration); // memcpy should be more efficient than 1 byte at a time
|
||||
}
|
||||
|
||||
spectrum_samples += buffer.count;
|
||||
if (spectrum_samples >= spectrum_interval_samples) {
|
||||
spectrum_samples -= spectrum_interval_samples;
|
||||
|
||||
txprogress_message.progress = bytes_read / 1024; // Inform UI about progress
|
||||
txprogress_message.progress = bytes_read; // Inform UI about progress
|
||||
|
||||
txprogress_message.done = false;
|
||||
shared_memory.application_queue.push(txprogress_message);
|
||||
|
||||
@@ -59,7 +59,7 @@ void ReplayProcessor::execute(const buffer_c8_t& buffer) {
|
||||
|
||||
// Fill and "stretch"
|
||||
for (size_t i = 0; i < buffer.count; i++) {
|
||||
if (i & 3) {
|
||||
if (i & 7) {
|
||||
buffer.p[i] = buffer.p[i - 1];
|
||||
} else {
|
||||
auto re_out = iq_buffer.p[i >> 3].real() >> 8;
|
||||
|
||||
+57
-12
@@ -22,26 +22,71 @@
|
||||
#ifndef __CONVERT_H__
|
||||
#define __CONVERT_H__
|
||||
|
||||
#include <charconv>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
/* Zero-allocation conversion helper. */
|
||||
/* Notes:
|
||||
* - T must be an integer type.
|
||||
* - For base 16, input _must not_ contain a '0x' or '0X' prefix.
|
||||
* - For base 8 a leading 0 on the literal is allowed.
|
||||
* - Leading whitespce will cause the parse to fail.
|
||||
*/
|
||||
// TODO: from_chars seems to cause code bloat.
|
||||
// Look into using strtol and friends instead.
|
||||
constexpr size_t max_parse_int_length = 64;
|
||||
|
||||
/* This undefined type is used to see the size of the
|
||||
* unhandled type and to create a compilation error. */
|
||||
template <size_t N>
|
||||
struct UnhandledSize;
|
||||
|
||||
/* Returns true when errno is ERANGE. */
|
||||
inline bool range_error() {
|
||||
return errno == ERANGE;
|
||||
}
|
||||
|
||||
/* Assigns 'value' to 'out_val' and returns true if 'value' is
|
||||
* in bounds for type TOut. */
|
||||
template <typename TVal, typename TOut>
|
||||
bool checked_assign(TVal value, TOut& out_val) {
|
||||
// No chance for overflow, just return.
|
||||
if constexpr (sizeof(TVal) <= sizeof(TOut)) {
|
||||
out_val = value;
|
||||
return true;
|
||||
}
|
||||
out_val = static_cast<TOut>(value);
|
||||
return value >= static_cast<TVal>(std::numeric_limits<TOut>::min()) &&
|
||||
value <= static_cast<TVal>(std::numeric_limits<TOut>::max());
|
||||
}
|
||||
|
||||
/* Zero-allocation conversion helper. 'str' must be smaller than 'max_parse_int_length'. */
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_integral_v<T>, bool> parse_int(std::string_view str, T& out_val, int base = 10) {
|
||||
auto result = std::from_chars(str.data(), str.data() + str.length(), out_val, base);
|
||||
return static_cast<int>(result.ec) == 0;
|
||||
// Always initialize the output.
|
||||
out_val = {};
|
||||
|
||||
if (str.size() > max_parse_int_length)
|
||||
return false;
|
||||
|
||||
// Copy onto the stack and null terminate.
|
||||
char zstr[max_parse_int_length + 1];
|
||||
std::memcpy(zstr, str.data(), str.size());
|
||||
zstr[str.size()] = '\0';
|
||||
|
||||
// A little C++ type magic to select the correct conversion function.
|
||||
if constexpr (sizeof(T) == sizeof(long long)) {
|
||||
if constexpr (std::is_unsigned_v<T>)
|
||||
return checked_assign(strtoull(zstr, nullptr, base), out_val) && !range_error();
|
||||
else
|
||||
return checked_assign(strtoll(zstr, nullptr, base), out_val) && !range_error();
|
||||
|
||||
} else if constexpr (sizeof(T) <= sizeof(long)) {
|
||||
if constexpr (std::is_unsigned_v<T>)
|
||||
return checked_assign(strtoul(zstr, nullptr, base), out_val) && !range_error();
|
||||
else
|
||||
return checked_assign(strtol(zstr, nullptr, base), out_val) && !range_error();
|
||||
} else {
|
||||
UnhandledSize<sizeof(T) * 8> unhandled_case;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /*__CONVERT_H__*/
|
||||
@@ -269,7 +269,7 @@ struct data_t {
|
||||
frequency_rx_correction(0),
|
||||
frequency_tx_correction(0),
|
||||
|
||||
encoder_dial_sensitivity(0),
|
||||
encoder_dial_sensitivity(DIAL_SENSITIVITY_NORMAL),
|
||||
UNUSED_8(0),
|
||||
headphone_volume_cb(-600),
|
||||
misc_config(),
|
||||
@@ -363,6 +363,7 @@ void defaults() {
|
||||
|
||||
set_config_backlight_timer(backlight_config_t{});
|
||||
set_config_splash(true);
|
||||
set_encoder_dial_sensitivity(DIAL_SENSITIVITY_NORMAL);
|
||||
|
||||
// Default values for recon app.
|
||||
set_recon_autosave_freqs(false);
|
||||
|
||||
@@ -107,9 +107,11 @@ struct backlight_config_t {
|
||||
};
|
||||
|
||||
enum encoder_dial_sensitivity {
|
||||
DIAL_SENSITIVITY_MEDIUM = 0,
|
||||
DIAL_SENSITIVITY_LOW = 1,
|
||||
DIAL_SENSITIVITY_HIGH = 2,
|
||||
DIAL_SENSITIVITY_HIGHEST = 0,
|
||||
DIAL_SENSITIVITY_HIGH = 1,
|
||||
DIAL_SENSITIVITY_NORMAL = 2,
|
||||
DIAL_SENSITIVITY_LOW = 3,
|
||||
DIAL_SENSITIVITY_LOWEST = 4,
|
||||
NUM_DIAL_SENSITIVITY
|
||||
};
|
||||
|
||||
|
||||
@@ -24,11 +24,26 @@
|
||||
namespace ui {
|
||||
|
||||
Glyph Font::glyph(const char c) const {
|
||||
size_t index;
|
||||
|
||||
if (c < c_start) {
|
||||
// Non-display C0 Control characters - map to blank (index 0)
|
||||
return {w, h, data};
|
||||
}
|
||||
const size_t index = c - c_start;
|
||||
if (index >= c_count) {
|
||||
|
||||
// Handle gap in glyphs table for C1 Control characters 0x80-0x9F
|
||||
if (c < C1_CONTROL_CHARS_START) {
|
||||
// ASCII chars <0x80:
|
||||
index = c - c_start;
|
||||
} else if (c >= C1_CONTROL_CHARS_START + C1_CONTROL_CHARS_COUNT) {
|
||||
// Latin 1 chars 0xA0-0xFF
|
||||
index = c - c_start - C1_CONTROL_CHARS_COUNT;
|
||||
} else {
|
||||
// C1 Control characters - map to blank
|
||||
return {w, h, data};
|
||||
}
|
||||
|
||||
if (index >= c_count) { // Latin Extended characters > 0xFF - not supported
|
||||
return {w, h, data};
|
||||
} else {
|
||||
return {w, h, &data[index * data_stride]};
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
|
||||
#include "ui.hpp"
|
||||
|
||||
// C1 Control Characters are an unprintable range of glyphs missing from the font files
|
||||
#define C1_CONTROL_CHARS_START 0x80
|
||||
#define C1_CONTROL_CHARS_COUNT 32
|
||||
|
||||
namespace ui {
|
||||
|
||||
class Glyph {
|
||||
|
||||
@@ -349,7 +349,7 @@ Text::Text(
|
||||
Rect parent_rect,
|
||||
std::string text)
|
||||
: Widget{parent_rect},
|
||||
text{text} {
|
||||
text{std::move(text)} {
|
||||
}
|
||||
|
||||
Text::Text(
|
||||
@@ -357,14 +357,14 @@ Text::Text(
|
||||
: Text{parent_rect, {}} {
|
||||
}
|
||||
|
||||
void Text::set(const std::string value) {
|
||||
text = value;
|
||||
void Text::set(std::string_view value) {
|
||||
text = std::string{value};
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void Text::paint(Painter& painter) {
|
||||
const auto rect = screen_rect();
|
||||
const auto s = style();
|
||||
auto s = has_focus() ? style().invert() : style();
|
||||
|
||||
painter.fill_rectangle(rect, s.background);
|
||||
|
||||
@@ -573,7 +573,7 @@ void ProgressBar::paint(Painter& painter) {
|
||||
const auto sr = screen_rect();
|
||||
const auto s = style();
|
||||
|
||||
v_scaled = (sr.size().width() * _value) / _max;
|
||||
v_scaled = (sr.size().width() * (uint64_t)_value) / _max;
|
||||
|
||||
painter.fill_rectangle({sr.location(), {v_scaled, sr.size().height()}}, style().foreground);
|
||||
painter.fill_rectangle({{sr.location().x() + v_scaled, sr.location().y()}, {sr.size().width() - v_scaled, sr.size().height()}}, s.background);
|
||||
@@ -1608,9 +1608,9 @@ bool OptionsField::on_touch(const TouchEvent event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* TextField ***********************************************************/
|
||||
/* TextEdit ***********************************************************/
|
||||
|
||||
TextField::TextField(
|
||||
TextEdit::TextEdit(
|
||||
std::string& str,
|
||||
size_t max_length,
|
||||
Point position,
|
||||
@@ -1624,24 +1624,24 @@ TextField::TextField(
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
const std::string& TextField::value() const {
|
||||
const std::string& TextEdit::value() const {
|
||||
return text_;
|
||||
}
|
||||
|
||||
void TextField::set_cursor(uint32_t pos) {
|
||||
void TextEdit::set_cursor(uint32_t pos) {
|
||||
cursor_pos_ = std::min<size_t>(pos, text_.length());
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::set_insert_mode() {
|
||||
void TextEdit::set_insert_mode() {
|
||||
insert_mode_ = true;
|
||||
}
|
||||
|
||||
void TextField::set_overwrite_mode() {
|
||||
void TextEdit::set_overwrite_mode() {
|
||||
insert_mode_ = false;
|
||||
}
|
||||
|
||||
void TextField::char_add(char c) {
|
||||
void TextEdit::char_add(char c) {
|
||||
// Don't add if inserting and at max_length and
|
||||
// don't overwrite if past the end of the text.
|
||||
if ((text_.length() >= max_length_ && insert_mode_) ||
|
||||
@@ -1657,7 +1657,7 @@ void TextField::char_add(char c) {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::char_delete() {
|
||||
void TextEdit::char_delete() {
|
||||
if (cursor_pos_ == 0)
|
||||
return;
|
||||
|
||||
@@ -1666,7 +1666,7 @@ void TextField::char_delete() {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextField::paint(Painter& painter) {
|
||||
void TextEdit::paint(Painter& painter) {
|
||||
constexpr int char_width = 8;
|
||||
|
||||
auto rect = screen_rect();
|
||||
@@ -1702,7 +1702,7 @@ void TextField::paint(Painter& painter) {
|
||||
painter.draw_rectangle(cursor_box, cursor_style.background);
|
||||
}
|
||||
|
||||
bool TextField::on_key(const KeyEvent key) {
|
||||
bool TextEdit::on_key(const KeyEvent key) {
|
||||
if (key == KeyEvent::Left && cursor_pos_ > 0)
|
||||
cursor_pos_--;
|
||||
else if (key == KeyEvent::Right && cursor_pos_ < text_.length())
|
||||
@@ -1716,7 +1716,7 @@ bool TextField::on_key(const KeyEvent key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextField::on_encoder(const EncoderEvent delta) {
|
||||
bool TextEdit::on_encoder(const EncoderEvent delta) {
|
||||
int32_t new_pos = cursor_pos_ + delta;
|
||||
|
||||
// Let the encoder wrap around the ends of the text.
|
||||
@@ -1729,7 +1729,7 @@ bool TextField::on_encoder(const EncoderEvent delta) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextField::on_touch(const TouchEvent event) {
|
||||
bool TextEdit::on_touch(const TouchEvent event) {
|
||||
if (event.type == TouchEvent::Type::Start)
|
||||
focus();
|
||||
|
||||
@@ -1737,6 +1737,32 @@ bool TextField::on_touch(const TouchEvent event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* TextField *************************************************************/
|
||||
|
||||
TextField::TextField(Rect parent_rect, std::string text)
|
||||
: Text(parent_rect, std::move(text)) {
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
const std::string& TextField::get_text() const {
|
||||
return text;
|
||||
}
|
||||
|
||||
void TextField::set_text(std::string_view value) {
|
||||
set(value);
|
||||
if (on_change)
|
||||
on_change(*this);
|
||||
}
|
||||
|
||||
bool TextField::on_key(KeyEvent key) {
|
||||
if (key == KeyEvent::Select && on_select) {
|
||||
on_select(*this);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* NumberField ***********************************************************/
|
||||
|
||||
NumberField::NumberField(
|
||||
|
||||
@@ -33,10 +33,11 @@
|
||||
#include "portapack.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -202,11 +203,11 @@ class Text : public Widget {
|
||||
Text(Rect parent_rect, std::string text);
|
||||
Text(Rect parent_rect);
|
||||
|
||||
void set(const std::string value);
|
||||
void set(std::string_view value);
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
protected:
|
||||
std::string text;
|
||||
};
|
||||
|
||||
@@ -619,6 +620,7 @@ class OptionsField : public Widget {
|
||||
|
||||
OptionsField(Point parent_pos, size_t length, options_t options);
|
||||
|
||||
options_t& options() { return options_; }
|
||||
const options_t& options() const { return options_; }
|
||||
void set_options(options_t new_options);
|
||||
|
||||
@@ -642,14 +644,14 @@ class OptionsField : public Widget {
|
||||
size_t selected_index_{0};
|
||||
};
|
||||
|
||||
// A TextField is bound to a string reference and allows the string
|
||||
// A TextEdit is bound to a string reference and allows the string
|
||||
// to be manipulated. The field itself does not provide the UI for
|
||||
// setting the value. It provides the UI of rendering the text,
|
||||
// a cursor, and an API to edit the string content.
|
||||
class TextField : public Widget {
|
||||
class TextEdit : public Widget {
|
||||
public:
|
||||
TextField(std::string& str, Point position, uint32_t length = 30)
|
||||
: TextField{str, 64, position, length} {}
|
||||
TextEdit(std::string& str, Point position, uint32_t length = 30)
|
||||
: TextEdit{str, 64, position, length} {}
|
||||
|
||||
// Str: the string containing the content to edit.
|
||||
// Max_length: max length the string is allowed to use.
|
||||
@@ -658,12 +660,12 @@ class TextField : public Widget {
|
||||
// - Characters are 8 pixels wide.
|
||||
// - The screen can show 30 characters max.
|
||||
// - The control is 16 pixels tall.
|
||||
TextField(std::string& str, size_t max_length, Point position, uint32_t length = 30);
|
||||
TextEdit(std::string& str, size_t max_length, Point position, uint32_t length = 30);
|
||||
|
||||
TextField(const TextField&) = delete;
|
||||
TextField(TextField&&) = delete;
|
||||
TextField& operator=(const TextField&) = delete;
|
||||
TextField& operator=(TextField&&) = delete;
|
||||
TextEdit(const TextEdit&) = delete;
|
||||
TextEdit(TextEdit&&) = delete;
|
||||
TextEdit& operator=(const TextEdit&) = delete;
|
||||
TextEdit& operator=(TextEdit&&) = delete;
|
||||
|
||||
const std::string& value() const;
|
||||
|
||||
@@ -688,6 +690,22 @@ class TextField : public Widget {
|
||||
bool insert_mode_;
|
||||
};
|
||||
|
||||
class TextField : public Text {
|
||||
public:
|
||||
std::function<void(TextField&)> on_select{};
|
||||
std::function<void(TextField&)> on_change{};
|
||||
|
||||
TextField(Rect parent_rect, std::string text);
|
||||
|
||||
const std::string& get_text() const;
|
||||
void set_text(std::string_view value);
|
||||
|
||||
bool on_key(KeyEvent key) override;
|
||||
|
||||
private:
|
||||
using Text::set;
|
||||
};
|
||||
|
||||
class NumberField : public Widget {
|
||||
public:
|
||||
std::function<void(NumberField&)> on_select{};
|
||||
|
||||
@@ -71,6 +71,7 @@ target_include_directories(application_test PRIVATE
|
||||
)
|
||||
|
||||
target_compile_options(application_test PRIVATE
|
||||
-std=c++17
|
||||
-DLPC43XX
|
||||
-DLPC43XX_M0
|
||||
-D__NEWLIB__
|
||||
|
||||
@@ -46,10 +46,11 @@ TEST_CASE("It should convert string_views.") {
|
||||
CHECK_EQ(val, 12345);
|
||||
}
|
||||
|
||||
TEST_CASE("It should return false for invalid input") {
|
||||
// 'from_chars' supported this, but 'strtol' just returns 0.
|
||||
/*TEST_CASE("It should return false for invalid input") {
|
||||
int val = 0;
|
||||
REQUIRE_FALSE(parse_int("QWERTY", val));
|
||||
}
|
||||
}*/
|
||||
|
||||
TEST_CASE("It should return false for overflow input") {
|
||||
uint8_t val = 0;
|
||||
@@ -58,7 +59,7 @@ TEST_CASE("It should return false for overflow input") {
|
||||
|
||||
TEST_CASE("It should convert base 16.") {
|
||||
int val = 0;
|
||||
REQUIRE(parse_int("30", val, 16)); // NB: No '0x'
|
||||
REQUIRE(parse_int("0x30", val, 16));
|
||||
CHECK_EQ(val, 0x30);
|
||||
}
|
||||
|
||||
@@ -74,4 +75,28 @@ TEST_CASE("It should convert as much of the string as it can.") {
|
||||
CHECK_EQ(val, 12345);
|
||||
}
|
||||
|
||||
TEST_CASE("It should convert 64-bit.") {
|
||||
int64_t val = 0;
|
||||
REQUIRE(parse_int("123456789", val));
|
||||
CHECK_EQ(val, 123456789);
|
||||
}
|
||||
|
||||
TEST_CASE("It should convert 32-bit.") {
|
||||
int32_t val = 0;
|
||||
REQUIRE(parse_int("123456", val));
|
||||
CHECK_EQ(val, 123456);
|
||||
}
|
||||
|
||||
TEST_CASE("It should convert 16-bit.") {
|
||||
int16_t val = 0;
|
||||
REQUIRE(parse_int("12345", val));
|
||||
CHECK_EQ(val, 12345);
|
||||
}
|
||||
|
||||
TEST_CASE("It should convert 8-bit.") {
|
||||
int8_t val = 0;
|
||||
REQUIRE(parse_int("123", val));
|
||||
CHECK_EQ(val, 123);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
@@ -32,13 +32,41 @@ TEST_CASE("to_string_dec_uint64 returns correct value.") {
|
||||
CHECK_EQ(to_string_dec_uint64(1'234'567'891), "1234567891");
|
||||
}
|
||||
|
||||
/*TEST_CASE("to_string_freq returns correct value.") {
|
||||
CHECK_EQ(to_string_freq(0), "0");
|
||||
CHECK_EQ(to_string_freq(1), "1");
|
||||
CHECK_EQ(to_string_freq(1'000'000), "1000000");
|
||||
TEST_CASE("to_string_freq returns correct value.") {
|
||||
CHECK_EQ(to_string_freq(0), " 0");
|
||||
CHECK_EQ(to_string_freq(1), " 1");
|
||||
CHECK_EQ(to_string_freq(1'000'000), " 1000000");
|
||||
CHECK_EQ(to_string_freq(1'234'567'890), "1234567890");
|
||||
CHECK_EQ(to_string_freq(1'234'567'891), "1234567891");
|
||||
}*/
|
||||
}
|
||||
|
||||
TEST_CASE("to_string_short_freq returns correct value.") {
|
||||
CHECK_EQ(to_string_short_freq(0), " 0.0000");
|
||||
CHECK_EQ(to_string_short_freq(49), " 0.0000");
|
||||
CHECK_EQ(to_string_short_freq(50), " 0.0001");
|
||||
CHECK_EQ(to_string_short_freq(1'000'049), " 1.0000");
|
||||
CHECK_EQ(to_string_short_freq(1'000'050), " 1.0001");
|
||||
CHECK_EQ(to_string_short_freq(1'234'567'850), "1234.5679");
|
||||
CHECK_EQ(to_string_short_freq(1'234'567'849), "1234.5678");
|
||||
}
|
||||
|
||||
TEST_CASE("to_string_rounded_freq returns correct value.") {
|
||||
CHECK_EQ(to_string_rounded_freq(0, 0), "0");
|
||||
CHECK_EQ(to_string_rounded_freq(0, 2), "0.00");
|
||||
CHECK_EQ(to_string_rounded_freq(49, 4), "0.0000");
|
||||
CHECK_EQ(to_string_rounded_freq(50, 4), "0.0001");
|
||||
CHECK_EQ(to_string_rounded_freq(23'456, 4), "0.0235");
|
||||
CHECK_EQ(to_string_rounded_freq(1'000'000, 4), "1.0000");
|
||||
CHECK_EQ(to_string_rounded_freq(1'000'567'849, 4), "1000.5678");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 0), "1234");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 1), "1234.6");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 2), "1234.57");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 3), "1234.568");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 4), "1234.5679");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 5), "1234.56789");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 6), "1234.567891");
|
||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 9), "1234.567891");
|
||||
}
|
||||
|
||||
TEST_CASE("trim removes whitespace.") {
|
||||
CHECK(trim(" foo\n") == "foo");
|
||||
@@ -47,3 +75,7 @@ TEST_CASE("trim removes whitespace.") {
|
||||
TEST_CASE("trim returns empty for only whitespace.") {
|
||||
CHECK(trim(" \n").empty());
|
||||
}
|
||||
|
||||
TEST_CASE("trim empty returns empty.") {
|
||||
CHECK(trim("").empty());
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
These fonts are for free and can be use for commercial and non-commercial purposes. These aren't professionally made--only beta test. Let me know if there are problems with spaces and kernings.
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2017 The Sedgwick Ave Project Authors (https://github.com/googlefonts/sedgwickave)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
,dannybernecker,dannys-mbp.speedport_w_724v_typ_a_05011603_00_007,16.12.2015 04:43,file:///Users/dannybernecker/Library/Application%20Support/LibreOffice/4;
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
@@ -0,0 +1,94 @@
|
||||
Copyright (c) <2006>, <Punkto Design> (<http://charaktertypen.com | info@charaktertypen.com>),
|
||||
with Reserved Font Name <„Beatstreet“>.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright (c) <2006>, <Punkto Design, D. Bernecker> (<http://charaktertypen.com | info@charaktertypen.com>), with Reserved Font Name <„Tagster“>.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,143 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=shift_jis">
|
||||
<TITLE>LARABIE FONTS “README</TITLE>
|
||||
<META NAME="GENERATOR" CONTENT="StarOffice 8 (Win32)">
|
||||
<META NAME="AUTHOR" CONTENT="Rina Larabie">
|
||||
<META NAME="CREATED" CONTENT="20061130;11580000">
|
||||
<META NAME="CHANGEDBY" CONTENT="Rina Larabie">
|
||||
<META NAME="CHANGED" CONTENT="20061207;15120000">
|
||||
<STYLE>
|
||||
<!--
|
||||
@page { size: 8.5in 11in; margin-right: 1.25in; margin-top: 1in; margin-bottom: 1in }
|
||||
P { margin-bottom: 0.08in; direction: ltr; color: #000000; widows: 2; orphans: 2 }
|
||||
P.western { font-family: "Times New Roman", serif; font-size: 12pt; so-language: en-US }
|
||||
P.cjk { font-family: "Times New Roman", serif; font-size: 12pt }
|
||||
P.ctl { font-family: "Times New Roman", serif; font-size: 12pt; so-language: ar-SA }
|
||||
H3 { margin-top: 0.19in; margin-bottom: 0.19in; direction: ltr; color: #000000; widows: 2; orphans: 2; page-break-after: auto }
|
||||
H3.western { font-size: 13pt; so-language: en-US }
|
||||
H3.cjk { font-family: "Times New Roman", serif; font-size: 13pt }
|
||||
H3.ctl { font-family: "Times New Roman", serif; font-size: 13pt; so-language: ar-SA }
|
||||
A:link { color: #0000ff }
|
||||
-->
|
||||
</STYLE>
|
||||
</HEAD>
|
||||
<BODY LANG="en-US" TEXT="#000000" LINK="#0000ff" DIR="LTR">
|
||||
<H3 CLASS="western" STYLE="margin-top: 0in"><FONT FACE="Tahoma, sans-serif">LARABIE
|
||||
FONTS FREEWARE FONTS EULA (End User License Agreement) and SOFTWARE
|
||||
INCLUSION AGREEMENT</FONT></H3>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><B><FONT FACE="Tahoma, sans-serif">LARABIE
|
||||
FONTS FREEWARE FONTS EULA</FONT></B></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Larabie
|
||||
Fonts Freeware Fonts in TrueType format are free to use for personal
|
||||
and commercial purposes. No payment is necessary to use Larabie Fonts
|
||||
Freeware Fonts for personal or commercial use. If you wish to include
|
||||
Larabie Fonts Freeware Fonts in software see SOFTWARE INCLUSION
|
||||
AGREEMENT below. </FONT></FONT>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2><B>USAGE</B></FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">You
|
||||
can offer individual Larabie Fonts Freeware Fonts for download on a
|
||||
website but do not combine fonts into a single archive or alter them
|
||||
in any way. We appreciate inclusion of the font name and trademark or
|
||||
site URL in the credits or documentation but it is not mandatory. </FONT></FONT>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Some
|
||||
Larabie Fonts Freeware Fonts may have enhanced and/or expanded
|
||||
families available for sale at </FONT></FONT><FONT COLOR="#0000ff"><U><A HREF="http://www.typodermic.com/"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">www.typodermic.com</FONT></FONT></A></U></FONT><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2><B>PAYMENT</B></FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Payment
|
||||
is not required for the use of Larabie Fonts Freeware Fonts unless
|
||||
they are intended to be <I>included</I> with software. More details
|
||||
follow.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Donations
|
||||
to Larabie Fonts are welcome via check or electronic payment via
|
||||
Paypal. We can accept any amount in US dollars, Canadian dollars, GBP
|
||||
or euros. If you require an invoice for your donation, please refer
|
||||
to </FONT></FONT><FONT COLOR="#0000ff"><U><A HREF="http://www.larabiefonts.com/donation.html"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">www.larabiefonts.com/donation.html</FONT></FONT></A></U></FONT><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">
|
||||
for instructions. </FONT></FONT>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Donations
|
||||
can include CDs, magazines, t-shirts, a sample of your merchandise or
|
||||
anything featuring Larabie Fonts. Mailing information is available at
|
||||
the link above.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2><B>SUPPORT</B></FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Font
|
||||
installation help is available at </FONT></FONT><FONT COLOR="#0000ff"><U><A HREF="http://www.myfonts.com/support"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">www.myfonts.com/support</FONT></FONT></A></U></FONT><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">.
|
||||
If you experience problems with any Larabie Font, please visit
|
||||
</FONT></FONT><FONT COLOR="#0000ff"><U><A HREF="http://www.larabiefonts.com/"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">www.larabiefonts.com</FONT></FONT></A></U></FONT><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">
|
||||
to verify you have the latest version. If you download Larabie Fonts
|
||||
Freeware Fonts from other websites you may get older versions that
|
||||
have spacing issues, incomplete character sets or technical problems.
|
||||
</FONT></FONT>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><B><FONT FACE="Tahoma, sans-serif">SOFTWARE
|
||||
INCLUSION AGREEMENT </FONT></B>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">The
|
||||
Larabie Fonts SOFTWARE PRODUCT is protected by copyright laws and
|
||||
International copyright treaties, as well as other intellectual
|
||||
property laws and treaties. The SOFTWARE PRODUCT is licensed, not
|
||||
sold.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">1.
|
||||
GRANT OF LICENSE. This document grants you the following rights:</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2>-
|
||||
Installation and Use. You may install and use an unlimited number of
|
||||
copies of the SOFTWARE PRODUCT. You may copy and distribute unlimited
|
||||
copies of the SOFTWARE PRODUCT as you receive them, in any medium,
|
||||
provided that you publish on each copy an appropriate copyright
|
||||
notice. Keep intact all the notices that refer to this License and
|
||||
give any other recipients of the fonts a copy of this License along
|
||||
with the fonts.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">2.
|
||||
LIMITED WARRANTY NO WARRANTIES. Larabie Fonts expressly disclaims any
|
||||
warranty for the SOFTWARE PRODUCT. The SOFTWARE PRODUCT and any
|
||||
related documentation is provided "as is" without warranty
|
||||
of any kind, either express or implied, including, without
|
||||
limitation, the implied warranties or merchantability, fitness for a
|
||||
particular purpose, or non-infringement. The entire risk arising out
|
||||
of use or performance of the SOFTWARE PRODUCT remains with you.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">NO
|
||||
LIABILITY FOR CONSEQUENTIAL DAMAGES. In no event shall Ray Larabie or
|
||||
Larabie Fonts be liable for any damages whatsoever (including,
|
||||
without limitation, damages for loss of business profits, business
|
||||
interruption, loss of business information, or any other pecuniary
|
||||
loss) arising out of the use of or inability to use this product,
|
||||
even if Larabie Fonts has been advised of the possibility of such
|
||||
damages.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2>3.
|
||||
MISCELLANEOUS</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">Should
|
||||
you have any questions concerning this document or you desire to
|
||||
contact Larabie Fonts for any reason, please email
|
||||
</FONT></FONT><FONT COLOR="#0000ff"><U><A HREF="http://www.larabiefonts.com/email.html"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">www.larabiefonts.com/email.html</FONT></FONT></A></U></FONT><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">.</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><BR>
|
||||
</P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT SIZE=2><FONT FACE="Tahoma, sans-serif">4.
|
||||
GOVERNING LAW</FONT></FONT></P>
|
||||
<P CLASS="western" STYLE="margin-bottom: 0in"><FONT FACE="Tahoma, sans-serif"><FONT SIZE=2>This
|
||||
agreement is governed by the laws of Canada and the province of
|
||||
British Columbia.</FONT></FONT></P>
|
||||
</BODY>
|
||||
</HTML>
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 2.6 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 723 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 721 KiB |
@@ -0,0 +1,456 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="2320px" height="2320px" viewBox="0 0 2320 2320" enable-background="new 0 0 2320 2320" xml:space="preserve">
|
||||
<rect x="0" y="0" width="2320" height="2320" fill="rgb(255,255,255)" /><g transform="translate(160,160)"><g transform="translate(640,0) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,0) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,0) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,0) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,0) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,80) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,80) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,80) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,80) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,80) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,160) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,160) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,160) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,160) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,160) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,240) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,240) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,240) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,240) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,240) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,320) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,320) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,320) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,400) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,400) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,400) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,400) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,400) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,480) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,480) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,480) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,480) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,480) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,560) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,560) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(0,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(160,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(480,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(560,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1760,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,640) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(0,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(80,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(240,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(400,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(560,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1680,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1840,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,720) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(80,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(160,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(400,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(480,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1680,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1760,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,800) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(80,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(400,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(560,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1680,880) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(0,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(80,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(480,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,960) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(80,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(320,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(400,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(560,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1840,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,1040) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(0,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(80,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(160,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(240,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(400,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(480,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(560,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1680,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1760,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,1120) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(160,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(560,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1680,1200) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(0,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(80,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(160,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(400,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(480,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1840,1280) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,1360) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,1360) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1360) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1360) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1360) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,1360) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,1440) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1520) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1520) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1520) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1520) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1840,1520) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1120,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1840,1600) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(800,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1040,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1760,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1840,1680) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1200,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1680,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1840,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,1760) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,1840) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1840) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,1840) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1520,1840) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,1840) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(640,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(720,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(880,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(960,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1280,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1360,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1440,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1680,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1920,1920) scale(0.8,0.8)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(0,0) scale(5.6, 5.6)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<g>
|
||||
<rect x="15" y="15" style="fill:none;" width="70" height="70"/>
|
||||
<path d="M85,0H15H0v15v70v15h15h70h15V85V15V0H85z M85,85H15V15h70V85z"/>
|
||||
</g>
|
||||
</g></g><g transform="translate(1440,0) scale(5.6, 5.6)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<g>
|
||||
<rect x="15" y="15" style="fill:none;" width="70" height="70"/>
|
||||
<path d="M85,0H15H0v15v70v15h15h70h15V85V15V0H85z M85,85H15V15h70V85z"/>
|
||||
</g>
|
||||
</g></g><g transform="translate(0,1440) scale(5.6, 5.6)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<g>
|
||||
<rect x="15" y="15" style="fill:none;" width="70" height="70"/>
|
||||
<path d="M85,0H15H0v15v70v15h15h70h15V85V15V0H85z M85,85H15V15h70V85z"/>
|
||||
</g>
|
||||
</g></g><g transform="translate(160,160) scale(2.4, 2.4)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(1600,160) scale(2.4, 2.4)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g><g transform="translate(160,1600) scale(2.4, 2.4)"><g transform="" style="fill: rgb(0, 0, 0);">
|
||||
<rect width="100" height="100"/>
|
||||
</g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 30 KiB |
Reference in New Issue
Block a user