Implement and archive Phase 5 form designer
This commit is contained in:
@@ -30,6 +30,9 @@ pub enum Execution {
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum WindowKind {
|
||||
Toolbox,
|
||||
Palette,
|
||||
MenuDesign,
|
||||
Code(ViewId),
|
||||
Project,
|
||||
Output,
|
||||
@@ -108,7 +111,7 @@ impl Field {
|
||||
pub fn flag(&self) -> bool {
|
||||
matches!(self.value, FieldValue::Toggle(true))
|
||||
}
|
||||
fn key(&mut self, key: KeyEvent) {
|
||||
pub(crate) fn key(&mut self, key: KeyEvent) {
|
||||
match &mut self.value {
|
||||
FieldValue::Toggle(value) => {
|
||||
if matches!(key.code, K::Char(' ') | K::Left | K::Right) {
|
||||
@@ -189,6 +192,11 @@ pub enum AfterSave {
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DialogKind {
|
||||
DesignProperty,
|
||||
DesignPalette,
|
||||
DesignMenu((String, Option<i32>)),
|
||||
DesignObject(Vec<u64>),
|
||||
DesignEvent(u64),
|
||||
Search {
|
||||
view: ViewId,
|
||||
selection: Option<std::ops::Range<usize>>,
|
||||
@@ -251,6 +259,10 @@ impl Dialog {
|
||||
}
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Hit {
|
||||
DesignTool(usize),
|
||||
DesignPaint(&'static str, u8),
|
||||
DesignObject(u64),
|
||||
DesignAction(&'static str),
|
||||
Command(Command),
|
||||
Menu(usize),
|
||||
MenuItem(usize),
|
||||
@@ -263,6 +275,7 @@ pub enum Hit {
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
pub designer: crate::designer::Designer,
|
||||
pub session: crate::execution::Session,
|
||||
pub editor: crate::editor::Editor,
|
||||
pub project: Project,
|
||||
@@ -301,6 +314,7 @@ impl App {
|
||||
let (options, errors, config_disk) = Options::load(&config_path);
|
||||
project.include_paths = options.include_paths.clone();
|
||||
let mut app = Self {
|
||||
designer: Default::default(),
|
||||
session: Default::default(),
|
||||
editor: Default::default(),
|
||||
project,
|
||||
@@ -389,6 +403,9 @@ impl App {
|
||||
pub fn active_document(&self) -> Option<DocumentId> {
|
||||
match self.active_window()?.kind {
|
||||
WindowKind::Code(v) => self.project.view(v).ok().map(|v| v.document()),
|
||||
WindowKind::Toolbox | WindowKind::Palette | WindowKind::MenuDesign => {
|
||||
self.designer.document
|
||||
}
|
||||
_ => self.project.members().get(self.selected_member).copied(),
|
||||
}
|
||||
}
|
||||
@@ -477,30 +494,33 @@ impl App {
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
if matches!(
|
||||
command,
|
||||
LoadText
|
||||
| Print
|
||||
| SaveText
|
||||
| Cut
|
||||
| Copy
|
||||
| Paste
|
||||
| Clear
|
||||
| NewSub
|
||||
| NewFunction
|
||||
| IncludedFile
|
||||
| IncludedLines
|
||||
| Find
|
||||
| SelectedText
|
||||
| FindNext
|
||||
| Replace
|
||||
| Procedures
|
||||
| PreviousCode
|
||||
| Diagnostics
|
||||
) && !matches!(
|
||||
self.active_window().map(|w| w.kind),
|
||||
Some(WindowKind::Code(_))
|
||||
) {
|
||||
if !(self.mode == Mode::Designer && matches!(command, Cut | Copy | Paste | Clear))
|
||||
&& matches!(
|
||||
command,
|
||||
LoadText
|
||||
| Print
|
||||
| SaveText
|
||||
| Cut
|
||||
| Copy
|
||||
| Paste
|
||||
| Clear
|
||||
| NewSub
|
||||
| NewFunction
|
||||
| IncludedFile
|
||||
| IncludedLines
|
||||
| Find
|
||||
| SelectedText
|
||||
| FindNext
|
||||
| Replace
|
||||
| Procedures
|
||||
| PreviousCode
|
||||
| Diagnostics
|
||||
)
|
||||
&& !matches!(
|
||||
self.active_window().map(|w| w.kind),
|
||||
Some(WindowKind::Code(_))
|
||||
)
|
||||
{
|
||||
return Some("Kein Codefenster aktiv".into());
|
||||
}
|
||||
if matches!(
|
||||
@@ -540,9 +560,19 @@ impl App {
|
||||
self.control_menu = false;
|
||||
if let Err(e) = self.action(command) {
|
||||
self.message = format!("{e:#}");
|
||||
if self.mode == Mode::Designer {
|
||||
self.open_dialog(
|
||||
"Designer",
|
||||
DialogKind::Message,
|
||||
vec![Field::text("Fehler", self.message.clone())],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn action(&mut self, command: Command) -> Result<()> {
|
||||
if self.designer_command(command)? {
|
||||
return Ok(());
|
||||
}
|
||||
use Command::*;
|
||||
let id = self.active_document();
|
||||
match command {
|
||||
@@ -742,6 +772,18 @@ impl App {
|
||||
FocusWindow(id) => {
|
||||
if self.windows.iter().any(|w| w.id == id) {
|
||||
self.active = id;
|
||||
if self.mode == Mode::Designer {
|
||||
if let Some(WindowKind::Code(v)) = self.active_window().map(|w| w.kind) {
|
||||
let doc = self.project.view(v)?.document();
|
||||
if matches!(self.project.document(doc)?.content(), Content::Form(_)) {
|
||||
self.design_enter(doc)?;
|
||||
} else {
|
||||
self.mode = Mode::Environment;
|
||||
self.properties = false;
|
||||
self.value_focus = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Calls | Debug | HelpWindow | Immediate | Output | Project => {
|
||||
@@ -930,6 +972,22 @@ impl App {
|
||||
};
|
||||
self.properties = form;
|
||||
self.value_focus = false;
|
||||
if form {
|
||||
self.design_enter(id)?;
|
||||
if !self.windows.iter().any(|w| w.kind == WindowKind::Toolbox) {
|
||||
let active = self.active;
|
||||
self.add_window(WindowKind::Toolbox, Rect::new(0, 1, 20, 21));
|
||||
if let Some(w) = self.windows.iter_mut().find(|w| w.id == active) {
|
||||
w.normal = Rect::new(
|
||||
20,
|
||||
1,
|
||||
self.size.0.saturating_sub(20),
|
||||
self.size.1.saturating_sub(1),
|
||||
);
|
||||
}
|
||||
self.active = active;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn cycle(&mut self, direction: isize) {
|
||||
@@ -942,7 +1000,9 @@ impl App {
|
||||
.position(|w| w.id == self.active)
|
||||
.unwrap_or(0);
|
||||
let i = (i as isize + direction).rem_euclid(self.windows.len() as isize) as usize;
|
||||
self.active = self.windows[i].id;
|
||||
if let Err(e) = self.action(Command::FocusWindow(self.windows[i].id)) {
|
||||
self.message = format!("{e:#}");
|
||||
}
|
||||
}
|
||||
fn arrange(&mut self) {
|
||||
let area = self.area();
|
||||
@@ -997,6 +1057,7 @@ impl App {
|
||||
}
|
||||
}
|
||||
self.session = Default::default();
|
||||
self.designer = Default::default();
|
||||
self.basic_events.clear();
|
||||
self.execution = Execution::Idle;
|
||||
self.base = self.project.directory().to_path_buf();
|
||||
@@ -1072,6 +1133,14 @@ impl App {
|
||||
}
|
||||
fn submit(&mut self, d: &mut Dialog) -> Result<bool> {
|
||||
match d.kind.clone() {
|
||||
DialogKind::DesignProperty
|
||||
| DialogKind::DesignPalette
|
||||
| DialogKind::DesignMenu(_)
|
||||
| DialogKind::DesignObject(_)
|
||||
| DialogKind::DesignEvent(_) => {
|
||||
self.design_submit(d)?;
|
||||
return Ok(false);
|
||||
}
|
||||
DialogKind::Search { .. }
|
||||
| DialogKind::Procedure(_)
|
||||
| DialogKind::Procedures(_)
|
||||
@@ -1350,6 +1419,19 @@ impl App {
|
||||
}
|
||||
self.key(key);
|
||||
} else if let Event::Mouse(mouse) = event {
|
||||
match self.design_mouse(mouse) {
|
||||
Ok(true) => return,
|
||||
Err(e) => {
|
||||
self.message = format!("{e:#}");
|
||||
self.open_dialog(
|
||||
"Designer",
|
||||
DialogKind::Message,
|
||||
vec![Field::text("Fehler", self.message.clone())],
|
||||
);
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if self.dialog.is_none() && self.menu.is_none() && self.program_focus() {
|
||||
if self.session.fullscreen {
|
||||
self.basic_events.push(Event::Mouse(mouse));
|
||||
@@ -1453,12 +1535,29 @@ impl App {
|
||||
return;
|
||||
}
|
||||
match hit {
|
||||
Hit::DesignTool(_) => {}
|
||||
Hit::DesignObject(id) => {
|
||||
if let Err(e) = self.design_select(id, false) {
|
||||
self.message = e.to_string();
|
||||
}
|
||||
}
|
||||
Hit::DesignAction(action) => {
|
||||
if let Err(e) = self.design_action(action) {
|
||||
self.message = format!("{e:#}");
|
||||
self.open_dialog(
|
||||
"Designer",
|
||||
DialogKind::Message,
|
||||
vec![Field::text("Fehler", self.message.clone())],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Hit::Command(c) => self.execute(c),
|
||||
Hit::Menu(i) => {
|
||||
self.properties = false;
|
||||
self.menu = Some((i, 0));
|
||||
}
|
||||
Hit::Window(id) => self.active = id,
|
||||
Hit::Window(id) => self.execute(Command::FocusWindow(id)),
|
||||
Hit::ProjectMember(i) => {
|
||||
self.selected_member = i;
|
||||
if let Some(w) = self.windows.iter().find(|w| w.kind == WindowKind::Project) {
|
||||
@@ -1674,19 +1773,61 @@ impl App {
|
||||
return;
|
||||
}
|
||||
if self.mode == Mode::Designer && key.code == K::F(2) {
|
||||
self.properties = true;
|
||||
self.value_focus = true;
|
||||
self.message = "Properties Value · Bearbeitung folgt in Change 05".into();
|
||||
if let Err(e) = self.design_value_focus() {
|
||||
self.message = format!("{e:#}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if self.program_focus() && key.modifiers.contains(M::CONTROL) && key.code == K::Char('c') {
|
||||
self.pause_execution();
|
||||
return;
|
||||
}
|
||||
if self.mode == Mode::Designer && self.value_focus {
|
||||
if let Err(e) = self.design_key(key) {
|
||||
self.message = format!("{e:#}");
|
||||
self.open_dialog(
|
||||
"Property",
|
||||
DialogKind::Message,
|
||||
vec![Field::text("Fehler", self.message.clone())],
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if matches!(
|
||||
self.active_window().map(|w| w.kind),
|
||||
Some(WindowKind::MenuDesign | WindowKind::Toolbox | WindowKind::Palette)
|
||||
) {
|
||||
match self.design_key(key) {
|
||||
Ok(true) => return,
|
||||
Err(e) => {
|
||||
self.message = format!("{e:#}");
|
||||
self.open_dialog(
|
||||
"Designer",
|
||||
DialogKind::Message,
|
||||
vec![Field::text("Fehler", self.message.clone())],
|
||||
);
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(command) = shortcut(key) {
|
||||
self.execute(command);
|
||||
return;
|
||||
}
|
||||
match self.design_key(key) {
|
||||
Ok(true) => return,
|
||||
Err(e) => {
|
||||
self.message = format!("{e:#}");
|
||||
self.open_dialog(
|
||||
"Designer",
|
||||
DialogKind::Message,
|
||||
vec![Field::text("Fehler", self.message.clone())],
|
||||
);
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if self.program_focus() {
|
||||
self.basic_events.push(Event::Key(key));
|
||||
return;
|
||||
@@ -1783,7 +1924,7 @@ impl App {
|
||||
))
|
||||
}
|
||||
fn code_key(&mut self, key: KeyEvent) -> Result<()> {
|
||||
if self.editor_view().is_err() {
|
||||
if self.mode == Mode::Designer || self.editor_view().is_err() {
|
||||
return Ok(());
|
||||
}
|
||||
self.editor_key(key)
|
||||
@@ -1803,7 +1944,7 @@ impl App {
|
||||
let mut entries = menus[index]
|
||||
.items
|
||||
.iter()
|
||||
.map(|i| (i.label.into(), i.command))
|
||||
.map(|i| (i.label.clone(), i.command))
|
||||
.collect::<Vec<_>>();
|
||||
if menus[index].title == "Window" {
|
||||
entries.extend(
|
||||
|
||||
@@ -101,7 +101,6 @@ impl Command {
|
||||
pub fn feature_phase(self) -> Option<u8> {
|
||||
use Command::*;
|
||||
match self {
|
||||
Events | Grid | Palette | MenuDesign | Toolbox | Tool(_) => Some(5),
|
||||
NextStatement | AddWatch | InstantWatch | Watchpoint | DeleteWatch | DeleteWatches
|
||||
| Trace | History | Breakpoint | ClearBreakpoints | BreakErrors | SetStatement
|
||||
| RunToCursor | Step | ProcedureStep | HistoryBack | HistoryForward => Some(6),
|
||||
@@ -113,7 +112,7 @@ impl Command {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Item {
|
||||
/// & markiert das sichtbare Mnemonic, … einen Dialog.
|
||||
pub label: &'static str,
|
||||
pub label: String,
|
||||
pub command: Option<Command>,
|
||||
}
|
||||
impl Item {
|
||||
@@ -136,12 +135,12 @@ pub struct Menu {
|
||||
}
|
||||
pub fn menus(designer: bool) -> Vec<Menu> {
|
||||
use Command::*;
|
||||
let item = |label, command| Item {
|
||||
label,
|
||||
let item = |label: &str, command| Item {
|
||||
label: label.into(),
|
||||
command: Some(command),
|
||||
};
|
||||
let sep = || Item {
|
||||
label: "────────",
|
||||
label: "────────".into(),
|
||||
command: None,
|
||||
};
|
||||
let menu = |title, mnemonic, items| Menu {
|
||||
@@ -211,27 +210,30 @@ pub fn menus(designer: bool) -> Vec<Menu> {
|
||||
result[2]
|
||||
.items
|
||||
.extend([sep(), item("&Menu Bar", MenuBar), item("&Grid Lines", Grid)]);
|
||||
result.push(menu(
|
||||
"Tools",
|
||||
't',
|
||||
vec![
|
||||
item("&Check Box", Tool("CheckBox")),
|
||||
item("C&ombo Box", Tool("ComboBox")),
|
||||
item("Command &Button", Tool("CommandButton")),
|
||||
item("&Dir List", Tool("DirListBox")),
|
||||
item("D&rive List", Tool("DriveListBox")),
|
||||
item("&File List", Tool("FileListBox")),
|
||||
item("Fr&ame", Tool("Frame")),
|
||||
item("&HScrollBar", Tool("HScrollBar")),
|
||||
item("&Label", Tool("Label")),
|
||||
item("L&ist Box", Tool("ListBox")),
|
||||
item("O&ption Button", Tool("OptionButton")),
|
||||
item("Pict&ure Box", Tool("PictureBox")),
|
||||
item("&Text Box", Tool("TextBox")),
|
||||
item("Ti&mer", Tool("Timer")),
|
||||
item("&VScrollBar", Tool("VScrollBar")),
|
||||
],
|
||||
));
|
||||
result.push(menu("Tools", 't', {
|
||||
let mut used = std::collections::BTreeSet::new();
|
||||
crate::designer::tools()
|
||||
.into_iter()
|
||||
.map(|(name, _, _)| {
|
||||
let mut label = name.to_owned();
|
||||
if let Some((at, c)) = name
|
||||
.char_indices()
|
||||
.find(|(_, c)| !used.contains(&c.to_ascii_lowercase()))
|
||||
{
|
||||
used.insert(c.to_ascii_lowercase());
|
||||
label.insert(at, '&');
|
||||
} else {
|
||||
let c = ('1'..='9').find(|c| !used.contains(c)).unwrap();
|
||||
used.insert(c);
|
||||
label = format!("&{c} {label}");
|
||||
}
|
||||
Item {
|
||||
label,
|
||||
command: Some(Tool(name)),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}));
|
||||
} else {
|
||||
result.extend([
|
||||
menu(
|
||||
|
||||
1925
crates/tb-ide/src/designer.rs
Normal file
1925
crates/tb-ide/src/designer.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -51,6 +51,8 @@ impl View {
|
||||
#[derive(Debug)]
|
||||
struct Edit {
|
||||
content: Content,
|
||||
design_ids: BTreeMap<(String, Option<i32>), u64>,
|
||||
group: Option<(u64, Vec<DocumentId>)>,
|
||||
views: Vec<(ViewId, View)>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
@@ -64,6 +66,7 @@ pub struct Document {
|
||||
binary_source: Option<PathBuf>,
|
||||
warnings: Vec<frm::BinaryWarning>,
|
||||
undo: Vec<Edit>,
|
||||
design_ids: BTreeMap<(String, Option<i32>), u64>,
|
||||
}
|
||||
impl Document {
|
||||
pub fn path(&self) -> Option<&Path> {
|
||||
@@ -257,6 +260,7 @@ impl Project {
|
||||
binary_source: read.binary.then_some(path),
|
||||
warnings: read.warnings,
|
||||
undo: Vec::new(),
|
||||
design_ids: BTreeMap::new(),
|
||||
},
|
||||
);
|
||||
Ok(id)
|
||||
@@ -308,6 +312,7 @@ impl Project {
|
||||
binary_source: None,
|
||||
warnings: Vec::new(),
|
||||
undo: Vec::new(),
|
||||
design_ids: BTreeMap::new(),
|
||||
},
|
||||
);
|
||||
self.manifest.lines.push(ProjectLine::File(path));
|
||||
@@ -344,6 +349,14 @@ impl Project {
|
||||
properties: BTreeMap::new(),
|
||||
children: Vec::new(),
|
||||
};
|
||||
let mut root = root;
|
||||
for (property, value) in [("WIDTH", 40), ("HEIGHT", 15)] {
|
||||
let p = tb_frontend::forms::property(root.class, property)
|
||||
.unwrap()
|
||||
.0;
|
||||
root.properties
|
||||
.insert(p, tb_ui::forms::PropertyValue::Integer(value));
|
||||
}
|
||||
self.create(
|
||||
name,
|
||||
Content::Form(Box::new(FormFile::new("1.00", root, ""))),
|
||||
@@ -423,7 +436,82 @@ impl Project {
|
||||
pub fn view_code(&self, id: ViewId) -> Result<&str> {
|
||||
Ok(self.document(self.view(id)?.document)?.code())
|
||||
}
|
||||
pub fn design_ids(&mut self, id: DocumentId) -> Result<&BTreeMap<(String, Option<i32>), u64>> {
|
||||
self.document(id)?;
|
||||
let doc = self.documents.get_mut(&id).unwrap();
|
||||
let mut keys = Vec::new();
|
||||
if let Content::Form(f) = &doc.content {
|
||||
fn visit(n: &FormNode, keys: &mut Vec<(String, Option<i32>)>) {
|
||||
keys.push(crate::designer::node_key(n));
|
||||
for child in &n.children {
|
||||
visit(child, keys);
|
||||
}
|
||||
}
|
||||
visit(&f.root, &mut keys);
|
||||
}
|
||||
doc.design_ids.retain(|k, _| keys.contains(k));
|
||||
for key in keys {
|
||||
doc.design_ids.entry(key).or_insert_with(next_id);
|
||||
}
|
||||
Ok(&doc.design_ids)
|
||||
}
|
||||
/// Alle Kandidaten zuerst prüfen; danach eine gemeinsame Undo-Einheit.
|
||||
pub(crate) fn commit_transaction(&mut self, changes: Vec<(DocumentId, Content)>) -> Result<()> {
|
||||
let mut ids = Vec::new();
|
||||
for (id, content) in &changes {
|
||||
let old = self.document(*id)?;
|
||||
ensure!(!ids.contains(id), "Doppeltes Dokument in Transaktion");
|
||||
if let Content::Form(f) = content {
|
||||
frm::read_text(&old.source_path.display().to_string(), &frm::write_text(f))?;
|
||||
}
|
||||
ids.push(*id);
|
||||
}
|
||||
let changes: Vec<_> = changes
|
||||
.into_iter()
|
||||
.filter(|(id, c)| self.documents[id].content != *c)
|
||||
.collect();
|
||||
let ids: Vec<_> = changes.iter().map(|(id, _)| *id).collect();
|
||||
let group = next_id();
|
||||
for (id, content) in changes {
|
||||
self.commit_edit(id, content)?;
|
||||
self.documents
|
||||
.get_mut(&id)
|
||||
.unwrap()
|
||||
.undo
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.group = Some((group, ids.clone()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub(crate) fn rekey_design_id(
|
||||
&mut self,
|
||||
id: DocumentId,
|
||||
old: &(String, Option<i32>),
|
||||
new: (String, Option<i32>),
|
||||
stable: u64,
|
||||
) {
|
||||
let doc = self.documents.get_mut(&id).unwrap();
|
||||
doc.design_ids.remove(old);
|
||||
doc.design_ids.insert(new, stable);
|
||||
}
|
||||
pub(crate) fn rename_design_ids(
|
||||
&mut self,
|
||||
id: DocumentId,
|
||||
old: &str,
|
||||
new: &str,
|
||||
before: &BTreeMap<(String, Option<i32>), u64>,
|
||||
) {
|
||||
let doc = self.documents.get_mut(&id).unwrap();
|
||||
for ((name, index), value) in before {
|
||||
if name.eq_ignore_ascii_case(old) {
|
||||
doc.design_ids.remove(&(name.clone(), *index));
|
||||
doc.design_ids.insert((new.to_uppercase(), *index), *value);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn commit_edit(&mut self, id: DocumentId, content: Content) -> Result<()> {
|
||||
self.design_ids(id)?;
|
||||
let old = self.document(id)?;
|
||||
if old.content == content {
|
||||
return Ok(());
|
||||
@@ -456,6 +544,8 @@ impl Project {
|
||||
};
|
||||
let undo = Edit {
|
||||
content: old.content.clone(),
|
||||
design_ids: old.design_ids.clone(),
|
||||
group: None,
|
||||
views: self
|
||||
.views
|
||||
.iter()
|
||||
@@ -546,12 +636,31 @@ impl Project {
|
||||
self.commit_edit(id, Content::Form(form))
|
||||
}
|
||||
pub fn undo(&mut self, id: DocumentId) -> Result<bool> {
|
||||
let group = self.document(id)?.undo.last().and_then(|e| e.group.clone());
|
||||
if let Some((number, ids)) = group {
|
||||
ensure!(
|
||||
ids.iter().all(|id| self.documents[id]
|
||||
.undo
|
||||
.last()
|
||||
.and_then(|e| e.group.as_ref())
|
||||
.is_some_and(|g| g.0 == number)),
|
||||
"Spätere Änderungen in verbundenen Dokumenten zuerst rückgängig machen"
|
||||
);
|
||||
for id in ids {
|
||||
self.undo_one(id)?;
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
self.undo_one(id)
|
||||
}
|
||||
fn undo_one(&mut self, id: DocumentId) -> Result<bool> {
|
||||
self.document(id)?;
|
||||
let doc = self.documents.get_mut(&id).unwrap();
|
||||
let Some(edit) = doc.undo.pop() else {
|
||||
return Ok(false);
|
||||
};
|
||||
doc.content = edit.content;
|
||||
doc.design_ids = edit.design_ids;
|
||||
doc.revision += 1;
|
||||
for (id, view) in edit.views {
|
||||
if let Some(current) = self.views.get_mut(&id) {
|
||||
|
||||
@@ -563,61 +563,14 @@ impl App {
|
||||
let v = self.editor_view()?;
|
||||
let view = self.project.view(v)?.clone();
|
||||
let id = view.document();
|
||||
let code = self.project.document(id)?.code();
|
||||
let input = self.project.sources()?;
|
||||
let name = d.fields[0].string();
|
||||
for unit in &input.units {
|
||||
let (module, _) = unit.parse(0, &mut Vec::new());
|
||||
ensure!(
|
||||
!module
|
||||
.procs
|
||||
.iter()
|
||||
.any(|p| p.sig.name.eq_ignore_ascii_case(&name)),
|
||||
"Prozedurname bereits im Projekt vorhanden"
|
||||
);
|
||||
}
|
||||
let path = self
|
||||
.project
|
||||
.document(id)?
|
||||
.source_path()
|
||||
.display()
|
||||
.to_string();
|
||||
let unit = input
|
||||
.units
|
||||
.iter()
|
||||
.find(|u| u.segments.iter().any(|s| s.file == path))
|
||||
.ok_or_else(|| anyhow!("Keine Übersetzungseinheit"))?;
|
||||
let first = match self.project.document(id)?.content() {
|
||||
tb_vm::project_io::Content::Form(f) => {
|
||||
tb_ui::frm::read_text(&path, &tb_ui::frm::write_text(f))?.code_line()
|
||||
}
|
||||
_ => 1,
|
||||
};
|
||||
let line =
|
||||
first + code[..view.cursor].bytes().filter(|b| *b == b'\n').count() as u32;
|
||||
let col = view.cursor - line_start(code, view.cursor);
|
||||
let mut expanded = String::new();
|
||||
let mut at = None;
|
||||
for segment in &unit.segments {
|
||||
for (i, text) in segment.text.split_inclusive('\n').enumerate() {
|
||||
if segment.file == path && segment.first_line + i as u32 == line {
|
||||
at = Some(
|
||||
expanded.len() + col.min(text.trim_end_matches(['\n', '\r']).len()),
|
||||
);
|
||||
}
|
||||
expanded.push_str(text);
|
||||
if !text.ends_with('\n') {
|
||||
expanded.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
let addition = tb_frontend::editing::new_procedure(
|
||||
&expanded,
|
||||
at.unwrap_or(expanded.len()),
|
||||
&name,
|
||||
let addition = self.new_procedure_text(
|
||||
id,
|
||||
view.cursor,
|
||||
&d.fields[0].string(),
|
||||
function,
|
||||
)
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
false,
|
||||
)?;
|
||||
let code = self.project.document(id)?.code();
|
||||
let end = code.len();
|
||||
self.project.replace_text(id, end..end, &addition)?;
|
||||
self.project.view_mut(v)?.cursor = end + 1;
|
||||
@@ -733,6 +686,75 @@ impl App {
|
||||
self.message = format!("{} Treffer ersetzt", ranges.len());
|
||||
self.editor_scroll()
|
||||
}
|
||||
pub(crate) fn new_procedure_text(
|
||||
&self,
|
||||
id: DocumentId,
|
||||
cursor: usize,
|
||||
name: &str,
|
||||
function: bool,
|
||||
module_local: bool,
|
||||
) -> Result<String> {
|
||||
let code = self.project.document(id)?.code();
|
||||
let input = self.project.sources()?;
|
||||
for unit in &input.units {
|
||||
if module_local
|
||||
&& !unit.segments.iter().any(|s| {
|
||||
s.file
|
||||
== self
|
||||
.project
|
||||
.document(id)
|
||||
.unwrap()
|
||||
.source_path()
|
||||
.display()
|
||||
.to_string()
|
||||
})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let (module, _) = unit.parse(0, &mut Vec::new());
|
||||
ensure!(
|
||||
!module
|
||||
.procs
|
||||
.iter()
|
||||
.any(|p| p.sig.name.eq_ignore_ascii_case(name)),
|
||||
"Prozedurname bereits im Projekt vorhanden"
|
||||
);
|
||||
}
|
||||
let path = self
|
||||
.project
|
||||
.document(id)?
|
||||
.source_path()
|
||||
.display()
|
||||
.to_string();
|
||||
let unit = input
|
||||
.units
|
||||
.iter()
|
||||
.find(|u| u.segments.iter().any(|s| s.file == path))
|
||||
.ok_or_else(|| anyhow!("Keine Übersetzungseinheit"))?;
|
||||
let first = match self.project.document(id)?.content() {
|
||||
tb_vm::project_io::Content::Form(f) => {
|
||||
tb_ui::frm::read_text(&path, &tb_ui::frm::write_text(f))?.code_line()
|
||||
}
|
||||
_ => 1,
|
||||
};
|
||||
let line = first + code[..cursor].bytes().filter(|b| *b == b'\n').count() as u32;
|
||||
let col = cursor - line_start(code, cursor);
|
||||
let mut expanded = String::new();
|
||||
let mut at = None;
|
||||
for segment in &unit.segments {
|
||||
for (i, text) in segment.text.split_inclusive('\n').enumerate() {
|
||||
if segment.file == path && segment.first_line + i as u32 == line {
|
||||
at = Some(expanded.len() + col.min(text.trim_end_matches(['\n', '\r']).len()));
|
||||
}
|
||||
expanded.push_str(text);
|
||||
if !text.ends_with('\n') {
|
||||
expanded.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
tb_frontend::editing::new_procedure(&expanded, at.unwrap_or(expanded.len()), name, function)
|
||||
.map_err(|e| anyhow!(e))
|
||||
}
|
||||
pub(crate) fn diagnostic_dialog(&mut self) {
|
||||
if !self.revision_current() {
|
||||
if let Err(e) = self.compile_current() {
|
||||
@@ -840,6 +862,9 @@ impl App {
|
||||
.then_some(self.editor.diagnostics.as_slice())
|
||||
}
|
||||
pub(crate) fn editor_position(&self) -> Option<(ViewId, DocumentId, usize)> {
|
||||
if self.mode == crate::app::Mode::Designer {
|
||||
return None;
|
||||
}
|
||||
let v = self.editor_view().ok()?;
|
||||
let view = self.project.view(v).ok()?;
|
||||
let code = self.project.document(view.document()).ok()?.code();
|
||||
|
||||
@@ -10,3 +10,5 @@ pub mod terminal;
|
||||
pub mod editor;
|
||||
|
||||
pub mod execution;
|
||||
|
||||
pub mod designer;
|
||||
|
||||
@@ -147,15 +147,10 @@ impl App {
|
||||
let v = self.project.view(view).unwrap();
|
||||
let doc = self.project.document(v.document()).unwrap();
|
||||
if self.mode == Mode::Designer
|
||||
&& active
|
||||
&& Some(v.document()) == self.designer.document
|
||||
&& matches!(doc.content(), tb_vm::project_io::Content::Form(_))
|
||||
{
|
||||
put(
|
||||
f,
|
||||
inner,
|
||||
"Formular · Designerwerkzeuge folgen in Change 05",
|
||||
content_style,
|
||||
);
|
||||
self.design_render(f, inner);
|
||||
} else {
|
||||
let expansion = self.editor.expansions.get(&view);
|
||||
let code = expansion.map(String::as_str).unwrap_or(doc.code());
|
||||
@@ -334,6 +329,9 @@ impl App {
|
||||
self.hits.push((r, Hit::ProjectMember(row)));
|
||||
}
|
||||
}
|
||||
kind @ (WindowKind::Toolbox | WindowKind::Palette | WindowKind::MenuDesign) => {
|
||||
self.design_tool_render(f, kind, inner)
|
||||
}
|
||||
WindowKind::Output => {
|
||||
f.render_widget(tb_ui::screen::ScreenWidget(self.session.screen()), inner)
|
||||
}
|
||||
@@ -400,22 +398,27 @@ impl App {
|
||||
);
|
||||
}
|
||||
if self.mode == Mode::Designer && self.properties {
|
||||
let (left, geometry) = self.design_bar();
|
||||
let width = (geometry.width() as u16 + 1).min(area.width);
|
||||
put(
|
||||
f,
|
||||
Rect::new(0, 0, area.width, 1),
|
||||
format!(
|
||||
"Property: [Caption]↓ Value: […]↓ │ Spalte,Zeile │ BxH{}",
|
||||
if self.value_focus {
|
||||
" · Value aktiv"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
),
|
||||
Rect::new(0, 0, area.width - width, 1),
|
||||
left,
|
||||
style(self, 0),
|
||||
);
|
||||
put(
|
||||
f,
|
||||
Rect::new(area.width - width, 0, width, 1),
|
||||
geometry,
|
||||
style(self, 0),
|
||||
);
|
||||
self.hits.push((
|
||||
Rect::new(0, 0, area.width, 1),
|
||||
Hit::Command(Command::MenuBar),
|
||||
Rect::new(0, 0, area.width / 2, 1),
|
||||
Hit::DesignAction("property"),
|
||||
));
|
||||
self.hits.push((
|
||||
Rect::new(area.width / 2, 0, area.width - area.width / 2, 1),
|
||||
Hit::DesignAction("value"),
|
||||
));
|
||||
} else {
|
||||
put(
|
||||
|
||||
Reference in New Issue
Block a user