UltimateZoneBuilder/Source/Core/Controls/ImageBrowserControl.cs
MaxED 6177c9cca9 Fixed, Texture Browser window: in some cases navigating the textures list using up/down arrows caused an exception.
Fixed, Map Analysis window: fixed an exception on copying warnings to clipboard when the clipboard was used by other application.
Fixed, TEXTURES parser: added special handling for "TNT1A0" sprite name so a texture with this sprite as a single patch is no longer treated as failed loading.
Fixed, Visual mode: in some cases (like line slopes used in several adjacent sectors) sector effect updates were triggered multiple times for the same sector, resulting in noticeable slowdowns and in some cases in infinite recursion.
Updated ZDoom ACC.
2016-02-15 14:06:46 +00:00

770 lines
21 KiB
C#

#region ================== Copyright (c) 2007 Pascal vd Heiden
/*
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
* This program is released under GNU General Public License
*
* 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.
*
*/
#endregion
#region ================== Namespaces
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.Windows;
#endregion
namespace CodeImp.DoomBuilder.Controls
{
internal partial class ImageBrowserControl : UserControl
{
#region ================== Constants
#endregion
#region ================== Delegates / Events
public delegate void SelectedItemChangedDelegate();
public delegate void SelectedItemDoubleClickDelegate();
public event SelectedItemChangedDelegate SelectedItemChanged;
public event SelectedItemDoubleClickDelegate SelectedItemDoubleClicked;
#endregion
#region ================== Variables
// Properties
private bool preventselection;
// States
private bool updating;
private int keepselected;
private bool browseFlats; //mxd
private static bool uselongtexturenames; //mxd
private static bool showtexturesfromsubdirs; //mxd
private int currentlevel; //mxd
// All items
private readonly List<ImageBrowserItem> items;
// Items visible in the list
private List<ImageBrowserItem> visibleitems;
//mxd
private static int mixMode;
#endregion
#region ================== Properties
public bool PreventSelection { get { return preventselection; } set { preventselection = value; } }
public bool HideInputBox { get { return splitter.Panel2Collapsed; } set { splitter.Panel2Collapsed = value; } }
public bool BrowseFlats { get { return browseFlats; } set { browseFlats = value; } } //mxd
public static bool ShowTexturesFromSubDirectories { get { return showtexturesfromsubdirs; } internal set { showtexturesfromsubdirs = value; } } //mxd
public static bool UseLongTextureNames { get { return uselongtexturenames; } internal set { uselongtexturenames = value; } } //mxd
public ListViewItem SelectedItem { get { if(list.SelectedItems.Count > 0) return list.SelectedItems[0]; else return null; } }
#endregion
#region ================== Constructor / Disposer
// Constructor
public ImageBrowserControl()
{
// Initialize
InitializeComponent();
items = new List<ImageBrowserItem>();
//mxd
StepsList sizes = new StepsList { 4, 8, 16, 32, 48, 64, 96, 128, 196, 256, 512, 1024 };
filterWidth.StepValues = sizes;
filterHeight.StepValues = sizes;
//mxd. Looks like SplitterDistance is unaffected by DPI scaling. Let's fix that...
if(MainForm.DPIScaler.Height != 1.0f)
{
splitter.SplitterDistance = splitter.Height - splitter.Panel2.Height - (int)Math.Round(splitter.SplitterWidth * MainForm.DPIScaler.Height);
}
}
// This applies the application settings
public void ApplySettings()
{
// Force black background?
if(General.Settings.BlackBrowsers)
{
list.BackColor = Color.Black;
list.ForeColor = Color.White;
}
// Set the size of preview images
if(General.Map != null)
{
int itemwidth = General.Map.Data.Previews.MaxImageWidth + 26;
int itemheight = General.Map.Data.Previews.MaxImageHeight + 26;
list.TileSize = new Size(itemwidth, itemheight);
//mxd
if(General.Map.Config.MixTexturesFlats)
{
cbMixMode.SelectedIndex = mixMode;
}
else
{
labelMixMode.Visible = false;
cbMixMode.Visible = false;
int offset = label.Left - labelMixMode.Left;
label.Left -= offset;
objectname.Left -= offset;
filterWidth.Left -= offset;
filterwidthlabel.Left -= offset;
filterHeight.Left -= offset;
filterheightlabel.Left -= offset;
showsubdirtextures.Left -= offset;
longtexturenames.Left -= offset;
mixMode = 0;
}
//mxd. Use long texture names?
longtexturenames.Checked = (uselongtexturenames && General.Map.Config.UseLongTextureNames);
longtexturenames.Visible = General.Map.Config.UseLongTextureNames;
if(!General.Map.Config.UseLongTextureNames)
showsubdirtextures.Left = longtexturenames.Left; //mxd
}
else
{
longtexturenames.Visible = false; //mxd
uselongtexturenames = false; //mxd
showsubdirtextures.Left = longtexturenames.Left; //mxd
}
//mxd
if(!General.Settings.CapitalizeTextureNames)
objectname.CharacterCasing = CharacterCasing.Normal;
//mxd. Show textures in subfolders?
showsubdirtextures.Checked = showtexturesfromsubdirs;
}
// This cleans everything up
public virtual void CleanUp()
{
// Stop refresh timer
refreshtimer.Enabled = false;
}
#endregion
#region ================== Rendering
// Draw item
private void list_DrawItem(object sender, DrawListViewItemEventArgs e)
{
if(!updating) (e.Item as ImageBrowserItem).Draw(e.Graphics, e.Bounds);
}
// Refresher
private void refreshtimer_Tick(object sender, EventArgs e)
{
bool allpreviewsloaded = true;
// Go for all items
foreach(ImageBrowserItem i in list.Items)
{
// Check if there are still previews that are not loaded
allpreviewsloaded &= i.IsPreviewLoaded;
// Items needs to be redrawn?
if(i.CheckRedrawNeeded())
{
// Refresh item in list
//list.RedrawItems(i.Index, i.Index, false);
list.Invalidate();
}
}
// If all previews were loaded, stop this timer
if(allpreviewsloaded) refreshtimer.Stop();
}
#endregion
#region ================== Events
// Name typed
private void objectname_TextChanged(object sender, EventArgs e)
{
// Update list
RefillList(false);
// No item selected?
if(list.SelectedItems.Count == 0)
{
// Select first
SelectFirstItem();
}
}
// Key pressed in textbox
private void objectname_KeyDown(object sender, KeyEventArgs e)
{
// Check what key is pressed
switch(e.KeyData)
{
// Cursor keys
case Keys.Left: SelectNextItem(SearchDirectionHint.Left); e.SuppressKeyPress = true; break;
case Keys.Right: SelectNextItem(SearchDirectionHint.Right); e.SuppressKeyPress = true; break;
case Keys.Up: SelectNextItem(SearchDirectionHint.Up); e.SuppressKeyPress = true; break;
case Keys.Down: SelectNextItem(SearchDirectionHint.Down); e.SuppressKeyPress = true; break;
// Tab
case Keys.Tab: GoToNextSameTexture(); e.SuppressKeyPress = true; break;
}
}
//mxd. Handle keyboard navigation the same way regardless of list being focused...
private void list_KeyDown(object sender, KeyEventArgs e)
{
// Check what key is pressed
switch(e.KeyData)
{
// Cursor keys
case Keys.Left: SelectNextItem(SearchDirectionHint.Left); e.SuppressKeyPress = true; break;
case Keys.Right: SelectNextItem(SearchDirectionHint.Right); e.SuppressKeyPress = true; break;
case Keys.Up: SelectNextItem(SearchDirectionHint.Up); e.SuppressKeyPress = true; break;
case Keys.Down: SelectNextItem(SearchDirectionHint.Down); e.SuppressKeyPress = true; break;
}
}
//mxd
private void filterSize_WhenTextChanged(object sender, EventArgs e)
{
objectname_TextChanged(sender, e);
}
//mxd
protected override bool ProcessTabKey(bool forward)
{
GoToNextSameTexture();
return false;
}
// Selection changed
private void list_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
if(!e.IsSelected) return; //mxd. Don't want to trigger this twice
// Prevent selecting?
if(preventselection)
{
foreach(ListViewItem i in list.SelectedItems) i.Selected = false;
}
else
{
// Raise event
if(SelectedItemChanged != null) SelectedItemChanged();
}
}
// Doublelicking an item
private void list_DoubleClick(object sender, EventArgs e)
{
if(!preventselection && (list.SelectedItems.Count > 0))
if(SelectedItemDoubleClicked != null) SelectedItemDoubleClicked();
}
//mxd. Transfer focus to Filter textbox
private void list_KeyPress(object sender, KeyPressEventArgs e)
{
if(!General.Settings.KeepTextureFilterFocused) return;
objectname.Focus();
if(e.KeyChar == '\b') // Any better way to check for Backspace?..
{
if(!string.IsNullOrEmpty(objectname.Text) && objectname.SelectionStart > 0 && objectname.SelectionLength == 0)
{
int s = objectname.SelectionStart - 1;
objectname.Text = objectname.Text.Remove(s, 1);
objectname.SelectionStart = s;
}
}
else
{
objectname.AppendText(e.KeyChar.ToString(CultureInfo.InvariantCulture));
}
}
//mxd
private void cbMixMode_SelectedIndexChanged(object sender, EventArgs e)
{
mixMode = cbMixMode.SelectedIndex;
RefillList(false);
}
//mxd
private void longtexturenames_CheckedChanged(object sender, EventArgs e)
{
uselongtexturenames = longtexturenames.Checked;
RefillList(false);
}
//mxd
private void showsubdirtextures_CheckedChanged(object sender, EventArgs e)
{
showtexturesfromsubdirs = showsubdirtextures.Checked;
RefillList(false);
}
#endregion
#region ================== Methods
// This selects the next texture with the same name as the selected texture
public void GoToNextSameTexture()
{
if(list.SelectedItems.Count > 0)
{
list.Focus(); //mxd
ListViewItem selected = list.SelectedItems[0];
//mxd
foreach(ImageBrowserItem n in visibleitems)
{
if(n == selected) continue;
if(n.Text == selected.Text)
{
if(list.IsGroupCollapsed(n.Group)) list.SetGroupCollapsed(n.Group, false);
n.Selected = true;
n.Focused = true;
n.EnsureVisible();
return;
}
}
}
}
// This selects an item by longname (mxd - changed from name to longname)
public void SelectItem(long longname, ListViewGroup preferredgroup)
{
ImageBrowserItem lvi = null; //mxd
// Not when selecting is prevented
if(preventselection) return;
// Search in preferred group first
if(preferredgroup != null)
{
foreach(ListViewItem item in list.Items)
{
ImageBrowserItem curitem = item as ImageBrowserItem;
if(curitem != null && longname == curitem.Icon.LongName) //mxd
{
lvi = curitem;
if(item.Group == preferredgroup) break;
}
}
}
// Select the item
if(lvi != null)
{
// Select this item
list.SelectedItems.Clear();
lvi.Selected = true;
lvi.EnsureVisible();
}
}
// This performs item sleection by keys
private void SelectNextItem(SearchDirectionHint dir)
{
// Not when selecting is prevented
if(preventselection) return;
// Nothing selected?
if(list.SelectedItems.Count == 0)
{
// Select first
SelectFirstItem();
}
else
{
//mxd
int index = list.SelectedItems[0].Index;
int targetindex = -1;
ListViewGroup startgroup = list.SelectedItems[0].Group;
Rectangle startrect = list.SelectedItems[0].GetBounds(ItemBoundsPortion.Entire);
switch(dir)
{
// Check previous items untill groups match...
case SearchDirectionHint.Left:
if(list.SelectedIndices[0] > 0)
{
while(--index > -1)
{
if(list.Items[index].Group == startgroup)
{
targetindex = index;
break;
}
}
}
break;
// Same thing, other direction...
case SearchDirectionHint.Right:
if(list.SelectedIndices[0] < list.Items.Count - 1)
{
while(++index < list.Items.Count)
{
if(list.Items[index].Group == startgroup)
{
targetindex = index;
break;
}
}
}
break;
// Check previous items untill X coordinate match and Y coordinate is less than the start ones...
case SearchDirectionHint.Up:
while(--index > -1)
{
ListViewItem item = list.Items[index];
if(item != null && item.Group == startgroup)
{
Rectangle rect = item.GetBounds(ItemBoundsPortion.Entire);
if(rect.X == startrect.X && rect.Y < startrect.Y)
{
targetindex = index;
break;
}
}
}
break;
// Same thing, other direction...
case SearchDirectionHint.Down:
if(list.SelectedIndices[0] < list.Items.Count - 1)
{
while(++index < list.Items.Count)
{
ListViewItem item = list.Items[index];
if(item != null && item.Group == startgroup)
{
Rectangle rect = item.GetBounds(ItemBoundsPortion.Entire);
if(rect.X == startrect.X && rect.Y > startrect.Y)
{
targetindex = index;
break;
}
}
}
}
break;
}
//mxd. Use the old method for Up/Down keys, becaue it can jump between Groups...
if(targetindex == -1 && (dir == SearchDirectionHint.Up || dir == SearchDirectionHint.Down))
{
Point spos = new Point(startrect.Location.X + startrect.Width / 2, startrect.Y + startrect.Height / 2);
// Try finding 5 times in the given direction
for(int i = 0; i < 5; i++)
{
// Move point in given direction
switch(dir)
{
case SearchDirectionHint.Up: spos.Y -= list.TileSize.Height / 2; break;
case SearchDirectionHint.Down: spos.Y += list.TileSize.Height / 2; break;
}
// Test position
ListViewItem lvi = list.GetItemAt(spos.X, spos.Y);
if(lvi != null)
{
targetindex = lvi.Index;
break;
}
}
}
//mxd. Found something?..
if(targetindex != -1)
{
// Select item
list.SelectedItems.Clear();
list.Items[targetindex].Selected = true;
list.SelectedItems[0].EnsureVisible();
}
}
}
// This selectes the first item
private void SelectFirstItem()
{
// Not when selecting is prevented
if(preventselection) return;
// Select first
if(list.Items.Count > 0)
{
list.SelectedItems.Clear();
ListViewItem lvi = list.GetItemAt(list.TileSize.Width / 2, list.TileSize.Height / 2);
if(lvi != null)
{
lvi.Selected = true;
lvi.EnsureVisible();
}
}
}
// This adds a group
public ListViewGroup AddGroup(string name)
{
ListViewGroup grp = new ListViewGroup(name);
list.Groups.Add(grp);
return grp;
}
//mxd
public bool IsGroupCollapsed(ListViewGroup group)
{
if(!list.Groups.Contains(group)) return false;
return list.IsGroupCollapsed(group);
}
//mxd. This enables group collapsability and optionally collapses it
public void SetGroupCollapsed(ListViewGroup group, bool collapse)
{
if(!list.Groups.Contains(group)) return;
list.SetGroupCollapsed(group, collapse);
}
// This begins adding items
public void BeginAdding(bool keepselectedindex) { BeginAdding(0, keepselectedindex); } //mxd
public void BeginAdding(int selectedlevel, bool keepselectedindex)
{
if(keepselectedindex && (list.SelectedItems.Count > 0))
keepselected = list.SelectedIndices[0];
else
keepselected = -1;
currentlevel = selectedlevel;
// Clean list
items.Clear();
// Stop updating
refreshtimer.Enabled = false;
}
// This ends adding items
public void EndAdding()
{
// Fill list with items
RefillList(true);
// Start updating
refreshtimer.Enabled = true;
}
// This adds an item
public void Add(ImageData image, object tag, ListViewGroup group)
{
ImageBrowserItem i = new ImageBrowserItem(image, tag, uselongtexturenames); //mxd
i.ListGroup = group;
i.Group = group;
i.ToolTipText = image.Name; //mxd
items.Add(i);
}
// This adds an item
public void Add(ImageData image, object tag, ListViewGroup group, string tooltiptext)
{
ImageBrowserItem i = new ImageBrowserItem(image, tag, uselongtexturenames); //mxd
i.ListGroup = group;
i.Group = group;
i.ToolTipText = tooltiptext;
items.Add(i);
}
// This fills the list based on the objectname filter
private void RefillList(bool selectfirst)
{
visibleitems = new List<ImageBrowserItem>();
//mxd. Store info about currently selected item
string selectedname = string.Empty;
ListViewGroup selecteditemgroup = null;
if(!selectfirst && keepselected == -1 && list.SelectedIndices.Count > 0)
{
selectedname = list.Items[list.SelectedIndices[0]].Text;
selecteditemgroup = list.Items[list.SelectedIndices[0]].Group;
}
// Begin updating list
updating = true;
//list.SuspendLayout();
list.BeginUpdate();
// Clear list first
// Group property of items will be set to null, we will restore it later
list.Items.Clear();
//mxd. Filtering by texture size?
int w = filterWidth.GetResult(-1);
int h = filterHeight.GetResult(-1);
// Go for all items
ImageBrowserItem previtem = null; //mxd
for(int i = items.Count - 1; i > -1; i--)
{
// Add item if valid
items[i].ShowFullName = uselongtexturenames; //mxd
if(ValidateItem(items[i], previtem) && ValidateItemSize(items[i], w, h))
{
items[i].Group = items[i].ListGroup;
items[i].Selected = false;
visibleitems.Add(items[i]);
previtem = items[i];
}
}
// Fill list
visibleitems.Sort();
ListViewItem[] array = new ListViewItem[visibleitems.Count];
for(int i = 0; i < visibleitems.Count; i++) array[i] = visibleitems[i];
list.Items.AddRange(array);
// Done updating list
updating = false;
list.EndUpdate();
list.Invalidate();
//list.ResumeLayout();
// Make selection?
if(!preventselection && (list.Items.Count > 0))
{
// Select specific item?
if(keepselected > -1)
{
list.Items[keepselected].Selected = true;
list.Items[keepselected].EnsureVisible();
}
// Select first item?
else if(selectfirst)
{
SelectFirstItem();
}
//mxd. Try reselecting the same/next closest item
else if(selecteditemgroup != null && !string.IsNullOrEmpty(selectedname))
{
ListViewItem bestmatch = null;
int charsmatched = 1;
foreach(ListViewItem item in list.Items)
{
if(item.Group == selecteditemgroup && item.Text[0] == selectedname[0])
{
if(item.Text == selectedname)
{
bestmatch = item;
break;
}
for(int i = 1; i < Math.Min(item.Text.Length, selectedname.Length); i++)
{
if(item.Text[i] != selectedname[i])
{
if(i > charsmatched)
{
bestmatch = item;
charsmatched = i;
}
break;
}
}
}
}
// Select the first item from the same group...
if(bestmatch == null)
{
foreach(ListViewItem item in list.Items)
{
if(item.Group == selecteditemgroup)
{
bestmatch = item;
break;
}
}
}
// Select found item
if(bestmatch != null)
{
bestmatch.Selected = true;
bestmatch.EnsureVisible();
}
}
}
// Raise event
if((SelectedItemChanged != null) && !preventselection) SelectedItemChanged();
}
// This validates an item
private bool ValidateItem(ImageBrowserItem item, ImageBrowserItem previtem)
{
//mxd. Don't show duplicate items
if(previtem != null && item.TextureName == previtem.TextureName && item.Group == previtem.Group) return false; //mxd
//mxd. mixMode: 0 = All, 1 = Textures, 2 = Flats, 3 = Based on BrowseFlats
if(!splitter.Panel2Collapsed)
{
if(mixMode == 1 && item.Icon.IsFlat) return false;
if(mixMode == 2 && !item.Icon.IsFlat) return false;
if(mixMode == 3 && (browseFlats != item.Icon.IsFlat)) return false;
if(!showtexturesfromsubdirs && item.Icon.Level > currentlevel) return false;
}
return item.Text.ToUpperInvariant().Contains(objectname.Text.ToUpperInvariant());
}
//mxd. This validates an item's texture size
private static bool ValidateItemSize(ImageBrowserItem i, int w, int h)
{
if(!i.Icon.IsPreviewLoaded) return true;
if(w > 0 && i.Icon.Width != w) return false;
if(h > 0 && i.Icon.Height != h) return false;
return true;
}
// This sends the focus to the textbox
public void FocusTextbox()
{
if(General.Settings.KeepTextureFilterFocused) //mxd
objectname.Focus();
else
list.Focus();
}
#endregion
}
}