Vlastní loga uživatele mimo instalační složku

Loga se dosud hledala jen v loga\ vedle .exe. Kdo si do číselníku přidal
firmu s vlastním logem, musel .dxf nakopírovat do instalační složky – tu ale
přeinstalace přepíše a v Program Files do ní nejde psát. Číselník firmu
přežil, logo ne.

Nově se hledá nejdřív v %APPDATA%\Rozpisky\loga a teprve pak mezi
dodávanými; nabídka v Údajích projektu je sjednocením obou složek (stejné
jméno se nezdvojuje). Tlačítko v Nastavení složku založí a otevře.

Hierarchie je stejná jako u číselníků a je to úmyslné: při konfliktu vyhrává
uživatelská vrstva. Kdyby vyhrávala dodávaná, uživatelova úprava by se tiše
neprojevila – on ji vidí uloženou, program pracuje s něčím jiným. Výjimkou
zůstávají data držící správnost výstupu (mapovani.json, Rozpiska.dxf), kde
vyhrává program.

Subagenti dostali explicitní model: render-dxf a wpf-ui sonnet,
verifikator haiku (jen spouští build a testy).

Build 0 chyb / 0 varování, 55 testů zelených (5 nových na slučování nabídky).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 11:13:13 +02:00
co-authored by Claude Opus 5
parent ba52486f53
commit cd2b82db3c
11 changed files with 191 additions and 25 deletions
+2 -1
View File
@@ -2,6 +2,7 @@
name: render-dxf
description: Specialista na kreslicí a DXF vrstvu Rozpisek (Rozpisky/Rendering/, Rozpisky/Dxf/). Použij pro změny v renderování (Skia, PNG, PDF, DXF výstup), ve čtečce DXF šablony, v mapování hodnot do polí, v kódech příloh a v práci s logy. Nepoužívej pro UI, ViewModely ani COM (CAD/Excel).
tools: Read, Edit, Write, Grep, Glob, Bash, PowerShell
model: sonnet
---
Jsi specialista na kreslicí jádro aplikace Rozpisky (C# / .NET 10, česky psaný kód).
@@ -50,6 +51,6 @@ Needituj produkční data: `Rozpisky/Podklady/Rozpiska.dxf`, `vzorky/schema.dxf`
3. Ověř:
`dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly` a
`dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q`
(baseline je 50 zelených testů — nesmí ubýt).
(baseline je 55 zelených testů — nesmí ubýt).
4. V odpovědi vrať: co jsi změnil (soubor:řádek), proč, a výsledek buildu a testů. Když něco
neprošlo, řekni to rovnou i s chybovou hláškou — nezakrývej to.
+2 -1
View File
@@ -2,6 +2,7 @@
name: verifikator
description: Read-only ověřovatel — spustí build a testy Rozpisek a vrátí strukturovaný verdikt. Použij po každé nezanedbatelné změně kódu, nebo když je potřeba nezávisle potvrdit, že projekt je v pořádku. Nic needituje.
tools: Read, Grep, Glob, Bash, PowerShell
model: haiku
---
Jsi nezávislý ověřovatel projektu Rozpisky. **Nesmíš nic měnit** — nemáš nástroje na editaci
@@ -17,7 +18,7 @@ dotnet test "C:\Users\marek\_Osobní\C sharp\EXE\Rozpisky\Rozpisky.Tests\Rozpis
Testy spusť i tehdy, když build projde s varováními. Když build spadne, testy nespouštěj
a rovnou hlas chyby buildu.
Baseline, se kterou porovnáváš: **build bez chyb a bez varování, 50 testů zelených, 0 přeskočených.**
Baseline, se kterou porovnáváš: **build bez chyb a bez varování, 55 testů zelených, 0 přeskočených.**
Volitelně (jen když o to volající požádá) ověř, že aplikace naběhne:
spusť `Rozpisky\bin\Debug\net10.0-windows\Rozpisky.exe`, počkej ~8 s, zkontroluj, že proces
+2 -1
View File
@@ -2,6 +2,7 @@
name: wpf-ui
description: Specialista na WPF prezentační vrstvu Rozpisek (ViewModels/, Views/, Themes/, Behaviors/, Converters/, Theming/). Použij pro změny obrazovek, XAML, bindingů, příkazů, chování gridů a vzhledu. Nepoužívej pro kreslicí/DXF jádro ani pro COM vrstvu.
tools: Read, Edit, Write, Grep, Glob, Bash, PowerShell
model: sonnet
---
Jsi specialista na WPF vrstvu aplikace Rozpisky (C# / .NET 10, MVVM bez frameworku, česky psaný kód).
@@ -35,7 +36,7 @@ nebo `Rozpisky/Xlsx/` jen popiš v odpovědi a nech je na volajícím.
začneš měnit UI.
2. `MainViewModel.cs` je velký; hledej v něm grepem, nečti ho celý zbytečně.
3. Ověř: `dotnet build "Rozpisky.sln" -nologo -v q -clp:ErrorsOnly` a
`dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q` (baseline 50 zelených).
`dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q` (baseline 55 zelených).
Build musí být **bez varování** — chyby v XAML se často projeví jen jako varování.
4. WPF chyby v bindingu build nezachytí. Pokud změna může binding rozbít, řekni to explicitně
a doporuč ruční spuštění aplikace.
+6 -3
View File
@@ -11,7 +11,7 @@ dotnet test "Rozpisky.Tests\Rozpisky.Tests.csproj" -nologo -v q
dotnet run --project "Rozpisky\Rozpisky.csproj"
```
Baseline: build bez chyb a varování, **50 testů zelených**. Před hlášením hotovo obojí spustit
Baseline: build bez chyb a varování, **55 testů zelených**. Před hlášením hotovo obojí spustit
(nebo použít `/overit`).
## Architektura — jediné pravidlo, které se nesmí porušit
@@ -38,8 +38,11 @@ jen relevantní kapitolu.
- Uživatelská data (číselníky, výběr CADu, logy) jdou přes
[`UzivatelskaData`](Rozpisky/Data/UzivatelskaData.cs) do `%APPDATA%\Rozpisky`. Do složky u `.exe`
se **nezapisuje** — může být jen pro čtení a přeinstalace ji přepíše. Číselníky navíc žijí ve dvou
vrstvách ([`CiselnikyStore`](Rozpisky/Data/CiselnikyStore.cs)): dodávaná + uložený rozdíl
uživatele. Změnu slučování vždy pokrýt testem — chyba tam se projeví tichou ztrátou dat.
vrstvách ([`CiselnikyStore`](Rozpisky/Data/CiselnikyStore.cs)) a loga ve dvou složkách
([`LogaResolver`](Rozpisky/Dxf/LogaResolver.cs)): dodávané + uživatelské navrch.
**Při konfliktu vyhrává uživatelská vrstva** — jinak by se jeho úprava tiše neprojevila.
Výjimka jsou data, která drží správnost výstupu (`mapovani.json`, `Rozpiska.dxf`) — tam vyhrává
program. Změnu slučování vždy pokrýt testem: chyba tam se projeví tichou ztrátou dat.
## Rozvržení složky — tři druhy podkladů
+80
View File
@@ -0,0 +1,80 @@
using System.IO;
using Rozpisky.Dxf;
using Xunit;
namespace Rozpisky.Tests;
/// <summary>
/// Nabídka log ze dvou složek (<see cref="LogaResolver.SlozNabidku"/>): dodávaná vedle .exe
/// a uživatelská, na kterou přeinstalace nesahá. Stejná hierarchie jako u číselníků
/// uživatelovo logo stejného jména přebíjí dodávané.
/// </summary>
public class LogaVrstvyTesty : IDisposable
{
private readonly string _koren = Path.Combine(Path.GetTempPath(), "rozpisky-loga-testy-" + Guid.NewGuid().ToString("N"));
private readonly string _dodavana;
private readonly string _uzivatelska;
public LogaVrstvyTesty()
{
_dodavana = Path.Combine(_koren, "dodavana");
_uzivatelska = Path.Combine(_koren, "uzivatelska");
Directory.CreateDirectory(_dodavana);
Directory.CreateDirectory(_uzivatelska);
}
public void Dispose()
{
try { Directory.Delete(_koren, recursive: true); } catch (IOException) { }
GC.SuppressFinalize(this);
}
private void Zaloz(string dir, params string[] soubory)
{
foreach (var s in soubory) File.WriteAllText(Path.Combine(dir, s), "0\nEOF\n");
}
[Fact]
public void BezUzivatelskychLogSeVratiJenDodavana()
{
Zaloz(_dodavana, "exprojekt.dxf", "mco.dxf");
Assert.Equal(new[] { "exprojekt.dxf", "mco.dxf" },
LogaResolver.SlozNabidku(_uzivatelska, _dodavana));
}
[Fact]
public void UzivatelskaLogaSeDoplniKDodavanym()
{
Zaloz(_dodavana, "exprojekt.dxf");
Zaloz(_uzivatelska, "nova_firma.dxf");
Assert.Equal(new[] { "exprojekt.dxf", "nova_firma.dxf" },
LogaResolver.SlozNabidku(_uzivatelska, _dodavana));
}
[Fact]
public void StejneJmenoSeVNabidceNezdvoji()
{
// Uživatel si opravil dodávané logo a uložil ho pod stejným jménem k sobě
// v comboboxu musí zůstat jedna položka, ne dvě nerozlišitelné.
Zaloz(_dodavana, "exprojekt.dxf");
Zaloz(_uzivatelska, "exprojekt.dxf");
Assert.Equal(new[] { "exprojekt.dxf" }, LogaResolver.SlozNabidku(_uzivatelska, _dodavana));
}
[Fact]
public void ChybejiciUzivatelskaSlozkaNevadi()
{
Zaloz(_dodavana, "exprojekt.dxf");
var neexistuje = Path.Combine(_koren, "tady-nic-neni");
Assert.Equal(new[] { "exprojekt.dxf" }, LogaResolver.SlozNabidku(neexistuje, _dodavana));
}
[Fact]
public void NabidkaJeAbecedniAIgnorujeJineNezDxf()
{
Zaloz(_dodavana, "tesia.dxf", "README.txt", "mco.dxf");
Zaloz(_uzivatelska, "alfa.dxf");
Assert.Equal(new[] { "alfa.dxf", "mco.dxf", "tesia.dxf" },
LogaResolver.SlozNabidku(_uzivatelska, _dodavana));
}
}
+54 -6
View File
@@ -1,4 +1,5 @@
using System.IO;
using System.IO;
using Rozpisky.Data;
using Rozpisky.Models;
namespace Rozpisky.Dxf;
@@ -8,19 +9,67 @@ namespace Rozpisky.Dxf;
/// Pravidla (kap. 4.3 manuálu SŽ):
/// <list type="bullet">
/// <item><c>Rozpiska_logo1/2</c> financující instituce (dotační vlevo / státní vpravo),
/// projektové, soubor ze složky <c>loga\</c> vedle .exe,</item>
/// projektové, soubor ze složky <c>loga\</c>,</item>
/// <item><c>Rozpiska_logo3</c> logo investora (číselník Organizace dle <c>Projekt.Investor</c>),</item>
/// <item><c>Rozpiska_logo4</c> logo zhotovitele díla (číselník Firmy dle <c>Projekt.ZhotovitelSOD</c>),</item>
/// <item><c>Rozpiska_logo5</c> logo zhotovitele objektu (číselník Firmy dle <c>Priloha.ZhotovitelCasti</c>),</item>
/// <item><c>Rozpiska_Orientační schéma</c> DXF u přílohy (<c>Priloha.SchemaSoubor</c>), relativně k .rzp.</item>
/// </list>
/// Položky bez vyplněného / neexistujícího souboru se vynechají.
/// <para>
/// Loga se hledají ve <b>dvou složkách</b> se stejnou logikou jako číselníky: nejdřív
/// <c>loga\</c> v uživatelské složce (viz <see cref="UzivatelskaData"/>), pak <c>loga\</c> vedle
/// .exe. Uživatelské logo stejného jména přebíjí dodávané. Bez toho by si uživatel, který přidá
/// do číselníku firmu s vlastním logem, musel soubor kopírovat do instalační složky tu ale
/// přeinstalace přepíše a v <c>Program Files</c> do ní nejde psát.
/// </para>
/// </summary>
public static class LogaResolver
{
/// <summary>Složka s logy dodávanými s programem (vedle .exe).</summary>
/// <summary>Složka s logy dodávanými s programem (vedle .exe). Jen ke čtení instalace ji přepisuje.</summary>
public static string LogaDir => Path.Combine(AppContext.BaseDirectory, "loga");
/// <summary>Složka pro loga, která si přidal uživatel. Přeinstalace na ni nesahá.</summary>
public static string UzivatelskaLogaDir => UzivatelskaData.Cesta("loga");
/// <summary>Najde soubor loga: absolutní cesta se bere, jak je, jinak se hledá nejdřív mezi
/// uživatelskými logy a pak mezi dodávanými. Vrací null, když soubor nikde není.</summary>
public static string? Najdi(string? soubor)
{
if (string.IsNullOrWhiteSpace(soubor)) return null;
if (Path.IsPathRooted(soubor)) return File.Exists(soubor) ? soubor : null;
var uzivatelske = Path.Combine(UzivatelskaLogaDir, soubor);
if (File.Exists(uzivatelske)) return uzivatelske;
var dodavane = Path.Combine(LogaDir, soubor);
return File.Exists(dodavane) ? dodavane : null;
}
/// <summary>Jména dostupných log z obou složek dohromady, abecedně. Uživatelské logo stejného
/// jména dodávané nezdvojuje v nabídce je jednou a použije se to uživatelovo.</summary>
public static IReadOnlyList<string> DostupnaLoga() => SlozNabidku(UzivatelskaLogaDir, LogaDir);
/// <summary>Vlastní slučování nabídky s cestami v parametru, ať jde otestovat bez profilu uživatele.</summary>
internal static List<string> SlozNabidku(string uzivatelskaDir, string dodavanaDir)
{
var jmena = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var dir in new[] { dodavanaDir, uzivatelskaDir })
{
try
{
if (!Directory.Exists(dir)) continue;
foreach (var soubor in Directory.GetFiles(dir, "*.dxf"))
jmena.Add(Path.GetFileName(soubor));
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
// Nedostupná složka (odpojený síťový disk apod.) nesmí shodit nabídku.
}
}
return jmena.ToList();
}
/// <param name="rzpDir">Složka aktuálního souboru .rzp (pro relativní cestu k schématu); null = neuloženo.</param>
public static IReadOnlyDictionary<string, string> Resolve(Projekt p, Priloha r, Ciselniky c, string? rzpDir)
{
@@ -40,9 +89,8 @@ public static class LogaResolver
private static void PridejZeSlozky(IDictionary<string, string> d, string hladina, string? soubor)
{
if (string.IsNullOrWhiteSpace(soubor)) return;
var cesta = Path.IsPathRooted(soubor) ? soubor : Path.Combine(LogaDir, soubor);
if (File.Exists(cesta)) d[hladina] = cesta;
var cesta = Najdi(soubor);
if (cesta is not null) d[hladina] = cesta;
}
/// <summary>Cesta ke schématu: absolutní beze změny, jinak relativně ke složce .rzp.</summary>
+24 -9
View File
@@ -215,20 +215,14 @@ public sealed class MainViewModel : ObservableObject
public IReadOnlyDictionary<string, string> ResolveLoga(Priloha r)
=> LogaResolver.Resolve(Projekt, r, Ciselniky, RzpSlozka);
/// <summary>Jména DXF souborů ve složce <c>loga\</c> vedle .exe (prázdná položka = „žádné")
/// nabídka pro výběr log financujících institucí v Údajích projektu.</summary>
/// <summary>Jména DXF log z obou složek <c>loga\</c> uživatelské i dodávané (prázdná položka
/// = „žádné") nabídka pro výběr log financujících institucí v Údajích projektu.</summary>
public IReadOnlyList<string> DostupnaLoga
{
get
{
var list = new List<string> { "" };
try
{
if (Directory.Exists(LogaResolver.LogaDir))
list.AddRange(Directory.GetFiles(LogaResolver.LogaDir, "*.dxf")
.Select(Path.GetFileName)!);
}
catch { /* nedostupná složka → jen prázdná položka */ }
list.AddRange(LogaResolver.DostupnaLoga());
return list;
}
}
@@ -311,6 +305,9 @@ public sealed class MainViewModel : ObservableObject
public ICommand ExportSeznamuCmd { get; }
public ICommand OtevritNapoveduCmd { get; }
public ICommand OtevritManualCmd { get; }
/// <summary>Otevře v Průzkumníku složku, kam si uživatel dává vlastní loga.</summary>
public ICommand OtevritSlozkuLogCmd { get; }
public ICommand OProgramuCmd { get; }
public MainViewModel()
@@ -407,6 +404,7 @@ public sealed class MainViewModel : ObservableObject
ExportSeznamuCmd = new RelayCommand(ExportujSeznamDoXlsx, () => Prilohy.Count > 0);
OtevritNapoveduCmd = new RelayCommand(OtevritNapovedu);
OtevritManualCmd = new RelayCommand(OtevritManual);
OtevritSlozkuLogCmd = new RelayCommand(OtevritSlozkuLog);
OProgramuCmd = new RelayCommand(OProgramu);
SledujZmeny();
@@ -1399,4 +1397,21 @@ public sealed class MainViewModel : ObservableObject
"Nápověda", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>Otevře v Průzkumníku složku pro uživatelská loga (a založí ji, když ještě není).
/// Vlastní loga patří sem, ne do složky programu tu přeinstalace přepíše.</summary>
private void OtevritSlozkuLog()
{
var slozka = LogaResolver.UzivatelskaLogaDir;
try
{
Directory.CreateDirectory(slozka);
Process.Start(new ProcessStartInfo(slozka) { UseShellExecute = true });
}
catch (Exception ex)
{
MessageBox.Show("Složku s logy se nepodařilo otevřít:\n" + slozka + "\n\n" + ex.Message,
"Loga", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
+10 -1
View File
@@ -1,4 +1,4 @@
<UserControl x:Class="Rozpisky.Views.NastaveniView"
<UserControl x:Class="Rozpisky.Views.NastaveniView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -28,6 +28,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Kořenová cesta exportu" Style="{StaticResource FieldLabel}"/>
@@ -39,6 +40,14 @@
<TextBlock Grid.Row="2" Grid.Column="0" Text="DXF šablona" Style="{StaticResource FieldLabel}"/>
<TextBox Grid.Row="2" Grid.Column="1" Margin="{StaticResource Inp}"
Text="{Binding Nastaveni.SablonaDxf, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Vlastní loga" Style="{StaticResource FieldLabel}"/>
<StackPanel Grid.Row="3" Grid.Column="1" Margin="{StaticResource Inp}">
<Button Content="Otevřít složku s logy" HorizontalAlignment="Left" Padding="10,0"
Command="{Binding OtevritSlozkuLogCmd}"/>
<TextBlock Style="{StaticResource Text.Caption}" TextWrapping="Wrap" Margin="0,4,0,0"
Text="Sem patří DXF loga, která si přidáš sám (třeba k nové firmě v číselníku). Přeinstalace programu na ně nesahá a v nabídce log mají přednost před dodávanými."/>
</StackPanel>
</Grid>
</GroupBox>
+2 -2
View File
@@ -1,4 +1,4 @@
<UserControl x:Class="Rozpisky.Views.UdajeProjektuView"
<UserControl x:Class="Rozpisky.Views.UdajeProjektuView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -146,7 +146,7 @@
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource Text.Caption}" Margin="0,0,0,6"
Text="Loga dotačních programů (vlevo) a státních institucí (vpravo). DXF soubory ze složky loga\ vedle programu."/>
Text="Loga dotačních programů (vlevo) a státních institucí (vpravo). DXF soubory dodávané s programem i vlastní (Nastavení → Otevřít složku s logy)."/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Logo dotační (vlevo)" Style="{StaticResource FieldLabel}"/>
<ComboBox Grid.Row="1" Grid.Column="1" Margin="{StaticResource Inp}"
+6
View File
@@ -341,6 +341,12 @@ v samostatné aplikaci.
staveb. **Není to** pole `K1``K47` (to je strojový kód, viz [§ 3.7](#37-kódové-označení-přílohy-47-pozic-pole-k1k47)).
- Pole pro **otisk razítek** / razítko oprávněné osoby (pole 3, kap. 4.3.3) zůstává v elektronické
verzi obvykle prázdné.
- **Kde loga leží** ve dvou složkách `loga\`, stejná hierarchie jako u číselníků
([3.4](#34-číselníky)): dodávaná vedle `.exe` a uživatelská v `%APPDATA%\Rozpisky\loga`, kterou
přeinstalace nepřepisuje. Hledá se nejdřív mezi uživatelskými, takže logo stejného jména přebíjí
dodávané. Nabídka v Údajích projektu je sjednocením obou; složku otevře tlačítko
v Nastavení. Bez toho by uživatel, který si do číselníku přidá firmu s vlastním logem, musel
soubor kopírovat do instalační složky a o to logo při aktualizaci přijít.
---
+3 -1
View File
@@ -78,7 +78,7 @@ Datové soubory jsou rozdělené podle toho, kdo je vlastní:
| Kde | Co | Kdo zapisuje |
|---|---|---|
| vedle `.exe` (zdroj `Rozpisky/Podklady/`) | `Rozpiska.dxf`, `SEZNAM.xlsx`, `ciselniky.vychozi.json`, `mapovani.json`, `loga/`, manuál SŽ (PDF) | nikdo dodává se s programem, každá instalace přepíše |
| `%APPDATA%\Rozpisky` ([`UzivatelskaData`](../Rozpisky/Data/UzivatelskaData.cs)) | `ciselniky.user.json`, `cad.json`, `layout.json`, `error.log`, `tisk_diag.log` | aplikace za běhu |
| `%APPDATA%\Rozpisky` ([`UzivatelskaData`](../Rozpisky/Data/UzivatelskaData.cs)) | `ciselniky.user.json`, `cad.json`, `layout.json`, `error.log`, `tisk_diag.log`, `loga/` (vlastní loga uživatele) | aplikace a uživatel |
| kdekoli u uživatele | projekty `.rzp` | aplikace na vyžádání |
Rozdělení existuje proto, aby přeinstalace novou verzí (nebo `.exe` v `Program Files`, kam se nesmí
@@ -218,6 +218,8 @@ Aplikace **nemá CLI režim** vše jede z GUI. Neočekávané výjimky zachy
dodávaný číselník měnit, aniž by uživatel přišel o svoje záznamy.
- **Migrace starých poloh** ([`Migrace`](../Rozpisky/Data/Migrace.cs)) data zapisovaná dřív vedle
.exe se při startu přenesou do uživatelské složky; originál se jen přejmenuje na `.migrovano`.
- **Loga ve dvou složkách** ([`LogaResolver`](../Rozpisky/Dxf/LogaResolver.cs)) hledá se nejdřív
v uživatelské `loga\`, pak v dodávané; nabídka v UI je sjednocením obou.
- **Zamčený cíl exportu** před zápisem PDF/XLSX/DXF se ověří, zda soubor nedrží jiný program
(Excel, prohlížeč PDF), a nabídne Pokračovat/Přerušit.