From 5b90c388fffdf03d33f7c28d6a00b81cbd9babf3 Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Thu, 13 Nov 2014 22:23:21 +0300 Subject: [PATCH 1/3] [log] IDEA-107117 Fix disappearing filter components Per nature of CustomComponentAction, don't store the state in the components itself. Instead store current filter state in the main ClassicFilterUi class, providing FilterModel classes to let components get and set filters' values. --- .../com/intellij/vcs/log/ui/VcsLogUiImpl.java | 9 +- .../ui/filter/BranchFilterPopupComponent.java | 49 ++--- .../ui/filter/DateFilterPopupComponent.java | 93 ++++----- .../vcs/log/ui/filter/FilterModel.java | 59 ++++++ .../log/ui/filter/FilterPopupComponent.java | 98 +++++----- .../log/ui/filter/MultilinePopupBuilder.java | 3 +- .../MultipleValueFilterPopupComponent.java | 54 ++---- .../filter/StructureFilterPopupComponent.java | 88 +++++---- .../ui/filter/UserFilterPopupComponent.java | 50 +++-- .../log/ui/filter/VcsLogClassicFilterUi.java | 183 +++++++++++------- .../intellij/vcs/log/ui/frame/MainFrame.java | 8 +- 11 files changed, 403 insertions(+), 291 deletions(-) create mode 100644 platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/FilterModel.java diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java index 7be1211ec70c..834d2c62ec55 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java @@ -279,7 +279,7 @@ public class VcsLogUiImpl implements VcsLogUi, Disposable { } private void commitNotFound(@NotNull String commitHash) { - if (getFilters().isEmpty()) { + if (myMainFrame.getFilterUi().getFilters().isEmpty()) { showMessage(MessageType.WARNING, "Commit " + commitHash + " not found"); } else { @@ -315,7 +315,7 @@ public class VcsLogUiImpl implements VcsLogUi, Disposable { } public void applyFiltersAndUpdateUi() { - myFilterer.onFiltersChange(getFilters()); + myFilterer.onFiltersChange(myMainFrame.getFilterUi().getFilters()); } @NotNull @@ -323,11 +323,6 @@ public class VcsLogUiImpl implements VcsLogUi, Disposable { return myFilterer; } - @NotNull - public VcsLogFilterCollection getFilters() { - return myMainFrame.getFilterUi().getFilters(); - } - public VcsLogGraphTable getTable() { return myMainFrame.getGraphTable(); } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/BranchFilterPopupComponent.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/BranchFilterPopupComponent.java index c70595bf7d1b..c66a075c3878 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/BranchFilterPopupComponent.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/BranchFilterPopupComponent.java @@ -32,19 +32,33 @@ import java.util.*; public class BranchFilterPopupComponent extends MultipleValueFilterPopupComponent { - @NotNull private final VcsLogUiProperties myUiProperties; - - @NotNull private VcsLogDataPack myDataPack; - - public BranchFilterPopupComponent(@NotNull VcsLogClassicFilterUi filterUi, @NotNull VcsLogDataPack dataPack, - @NotNull VcsLogUiProperties uiProperties) { - super(filterUi, "Branch"); - myDataPack = dataPack; - myUiProperties = uiProperties; + public BranchFilterPopupComponent(@NotNull VcsLogUiProperties uiProperties, + @NotNull FilterModel filterModel) { + super("Branch", uiProperties, filterModel); } - void updateDataPack(@NotNull VcsLogDataPack dataPack) { - myDataPack = dataPack; + @NotNull + @Override + protected String getText(@NotNull VcsLogBranchFilter filter) { + return displayableText(filter.getBranchNames()); + } + + @Nullable + @Override + protected String getToolTip(@NotNull VcsLogBranchFilter filter) { + return tooltip(filter.getBranchNames()); + } + + @NotNull + @Override + protected VcsLogBranchFilter createFilter(@NotNull Collection values) { + return new VcsLogBranchFilterImpl(values); + } + + @Override + @NotNull + protected Collection getValues(@Nullable VcsLogBranchFilter filter) { + return filter == null ? Collections.emptySet() : filter.getBranchNames(); } @Override @@ -54,7 +68,7 @@ public class BranchFilterPopupComponent extends MultipleValueFilterPopupComponen actionGroup.add(createAllAction()); actionGroup.add(createSelectMultipleValuesAction()); - actionGroup.add(constructActionGroup(myDataPack, createRecentItemsActionGroup(), new Function() { + actionGroup.add(constructActionGroup(myFilterModel.getDataPack(), createRecentItemsActionGroup(), new Function() { @Override public AnAction fun(String name) { return createPredefinedValueAction(Collections.singleton(name)); @@ -151,15 +165,6 @@ public class BranchFilterPopupComponent extends MultipleValueFilterPopupComponen } } - @Nullable - @Override - protected VcsLogBranchFilter getFilter() { - if (getSelectedValues() == null) { - return null; - } - return new VcsLogBranchFilterImpl(getSelectedValues()); - } - @NotNull @Override protected List> getRecentValuesFromSettings() { @@ -176,7 +181,7 @@ public class BranchFilterPopupComponent extends MultipleValueFilterPopupComponen @NotNull @Override protected List getAllValues() { - return ContainerUtil.map(myDataPack.getRefs().getBranches(), new Function() { + return ContainerUtil.map(myFilterModel.getDataPack().getRefs().getBranches(), new Function() { @Override public String fun(VcsRef ref) { return ref.getName(); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/DateFilterPopupComponent.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/DateFilterPopupComponent.java index 855b9266f12a..dc6c2c8b042c 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/DateFilterPopupComponent.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/DateFilterPopupComponent.java @@ -22,6 +22,7 @@ import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.ui.DialogBuilder; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.vcs.versionBrowser.DateFilterComponent; +import com.intellij.util.ObjectUtils; import com.intellij.util.text.DateFormatUtil; import com.intellij.vcs.log.VcsLogDateFilter; import com.intellij.vcs.log.data.VcsLogDateFilterImpl; @@ -33,11 +34,30 @@ import java.util.Date; class DateFilterPopupComponent extends FilterPopupComponent { - private Date myAfter; - private Date myBefore; + public DateFilterPopupComponent(FilterModel filterModel) { + super("Date", filterModel); + } - DateFilterPopupComponent(@NotNull VcsLogClassicFilterUi filterUi) { - super(filterUi, "Date"); + @NotNull + @Override + protected String getText(@NotNull VcsLogDateFilter filter) { + Date after = filter.getAfter(); + Date before = filter.getBefore(); + if (after != null && before != null) { + return DateFormatUtil.formatDate(after) + "-" + DateFormatUtil.formatDate(before); + } + else if (after != null) { + return "Since " + DateFormatUtil.formatDate(after); + } + else { + return "Until " + DateFormatUtil.formatDate(ObjectUtils.assertNotNull(before)); + } + } + + @Nullable + @Override + protected String getToolTip(@NotNull VcsLogDateFilter filter) { + return null; } @Override @@ -49,48 +69,24 @@ class DateFilterPopupComponent extends FilterPopupComponent { cal.add(Calendar.DAY_OF_YEAR, -6); Date oneWeekBefore = cal.getTime(); - DumbAwareAction allAction = new DumbAwareAction(ALL) { - @Override - public void actionPerformed(AnActionEvent e) { - myAfter = null; - myBefore = null; - applyFilters(); - setValue(ALL); - } - }; - return new DefaultActionGroup(allAction, + return new DefaultActionGroup(createAllAction(), new DateAction(oneDayBefore, "Last 24 hours"), new DateAction(oneWeekBefore, "Last 7 days"), new SelectAction()); } - @Nullable - @Override - protected VcsLogDateFilter getFilter() { - return myAfter == null && myBefore == null ? null : new VcsLogDateFilterImpl(myAfter, myBefore); - } - - private void setOnlyAfter(Date after) { - myAfter = after; - myBefore = null; - } - private class DateAction extends DumbAwareAction { - private final Date mySince; - private final String myText; + @NotNull private final Date mySince; - DateAction(Date since, String text) { + DateAction(@NotNull Date since, @NotNull String text) { super(text); mySince = since; - myText = text; } @Override - public void actionPerformed(AnActionEvent e) { - setValue(myText); - setOnlyAfter(mySince); - applyFilters(); + public void actionPerformed(@NotNull AnActionEvent e) { + myFilterModel.setFilter(new VcsLogDateFilterImpl(mySince, null)); } } @@ -101,13 +97,16 @@ class DateFilterPopupComponent extends FilterPopupComponent { } @Override - public void actionPerformed(AnActionEvent e) { + public void actionPerformed(@NotNull AnActionEvent e) { final DateFilterComponent dateComponent = new DateFilterComponent(false, DateFormatUtil.getDateFormat().getDelegate()); - if (myBefore != null) { - dateComponent.setBefore(myBefore.getTime()); - } - if (myAfter != null) { - dateComponent.setAfter(myAfter.getTime()); + VcsLogDateFilter currentFilter = myFilterModel.getFilter(); + if (currentFilter != null) { + if (currentFilter.getBefore() != null) { + dateComponent.setBefore(currentFilter.getBefore().getTime()); + } + if (currentFilter.getAfter() != null) { + dateComponent.setAfter(currentFilter.getAfter().getTime()); + } } DialogBuilder db = new DialogBuilder(DateFilterPopupComponent.this); @@ -118,20 +117,8 @@ class DateFilterPopupComponent extends FilterPopupComponent { if (DialogWrapper.OK_EXIT_CODE == db.show()) { long after = dateComponent.getAfter(); long before = dateComponent.getBefore(); - myAfter = after > 0 ? new Date(after) : null; - myBefore = before > 0 ? new Date(before) : null; - - if (myAfter != null && myBefore != null) { - setValue(DateFormatUtil.formatDate(after) + "-" + DateFormatUtil.formatDate(before)); - } - else if (myAfter != null) { - setValue("After " + DateFormatUtil.formatDate(after)); - } - else { - setValue("Before " + DateFormatUtil.formatDate(before)); - } - - applyFilters(); + VcsLogDateFilter filter = new VcsLogDateFilterImpl(after > 0 ? new Date(after) : null, before > 0 ? new Date(before) : null); + myFilterModel.setFilter(filter); } } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/FilterModel.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/FilterModel.java new file mode 100644 index 000000000000..4d32ceaa6897 --- /dev/null +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/FilterModel.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.vcs.log.ui.filter; + +import com.intellij.openapi.util.Computable; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.vcs.log.VcsLogDataPack; +import com.intellij.vcs.log.VcsLogFilter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +class FilterModel { + + @NotNull private final Computable myDataPackProvider; + @NotNull private final Collection mySetFilterListeners = ContainerUtil.newArrayList(); + + @Nullable private Filter myFilter; + + FilterModel(@NotNull Computable provider) { + myDataPackProvider = provider; + } + + void setFilter(@Nullable Filter filter) { + myFilter = filter; + for (Runnable listener : mySetFilterListeners) { + listener.run(); + } + } + + @Nullable + Filter getFilter() { + return myFilter; + } + + @NotNull + VcsLogDataPack getDataPack() { + return myDataPackProvider.compute(); + } + + void addSetFilterListener(@NotNull Runnable runnable) { + mySetFilterListeners.add(runnable); + } + +} diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/FilterPopupComponent.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/FilterPopupComponent.java index c30ebb546af0..00283950b9c2 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/FilterPopupComponent.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/FilterPopupComponent.java @@ -52,12 +52,19 @@ abstract class FilterPopupComponent extends JPanel @NotNull private final JLabel myFilterNameLabel; @NotNull private final JLabel myFilterValueLabel; - @NotNull private final VcsLogClassicFilterUi myFilterUi; + @NotNull protected final FilterModel myFilterModel; - FilterPopupComponent(@NotNull VcsLogClassicFilterUi filterUi, @NotNull String filterName) { - myFilterUi = filterUi; + FilterPopupComponent(@NotNull String filterName, + @NotNull FilterModel filterModel) { + myFilterModel = filterModel; myFilterNameLabel = new JLabel(filterName + ": "); - myFilterValueLabel = new JLabel(ALL); + myFilterValueLabel = new JLabel() { + @Override + public String getText() { + Filter filter = myFilterModel.getFilter(); + return filter == null ? ALL : FilterPopupComponent.this.getText(filter); + } + }; setDefaultForeground(); setFocusable(true); setBorder(UNFOCUSED_BORDER); @@ -68,12 +75,35 @@ abstract class FilterPopupComponent extends JPanel add(Box.createHorizontalStrut(GAP_BEFORE_ARROW)); add(new JLabel(AllIcons.Ide.Statusbar_arrows)); + updateLabelOnFilterChange(); showPopupMenuOnClick(); showPopupMenuFromKeyboard(); indicateHovering(); indicateFocusing(); } + private void updateLabelOnFilterChange() { + myFilterModel.addSetFilterListener(new Runnable() { + @Override + public void run() { + myFilterValueLabel.revalidate(); + myFilterValueLabel.repaint(); + } + }); + } + + @NotNull + protected abstract String getText(@NotNull Filter filter); + + @Nullable + protected abstract String getToolTip(@NotNull Filter filter); + + @Override + public String getToolTipText() { + Filter filter = myFilterModel.getFilter(); + return filter == null ? null : getToolTip(filter); + } + private static Border createFocusedBorder() { return BorderFactory.createCompoundBorder(new RoundedLineBorder(UIUtil.getHeaderActiveColor(), 10, BORDER_SIZE), INNER_MARGIN_BORDER); @@ -84,52 +114,29 @@ abstract class FilterPopupComponent extends JPanel INNER_MARGIN_BORDER); } + /** * Create popup actions available under this filter. */ protected abstract ActionGroup createActionGroup(); - /** - * Return the filter currently selected via this component or null if no filters are selected via this component. - */ - @Nullable - protected abstract Filter getFilter(); - - protected void setValue(@NotNull String newValue) { - setValue(newValue, newValue); - } - - protected void setValue(@NotNull String value, @NotNull String tooltip) { - myFilterValueLabel.setText(value); - setToolTipText(tooltip.equals(value) ? null : tooltip); - } - - @NotNull - public String getValue() { - return myFilterValueLabel.getText(); - } - - protected void applyFilters() { - myFilterUi.applyFilters(); - } - /** * Returns the special action that indicates that no filtering is selected in this component. */ @NotNull protected AnAction createAllAction() { - return new AllAction(this); + return new AllAction(); } private void indicateFocusing() { addFocusListener(new FocusAdapter() { @Override - public void focusGained(FocusEvent e) { + public void focusGained(@NotNull FocusEvent e) { setBorder(FOCUSED_BORDER); } @Override - public void focusLost(FocusEvent e) { + public void focusLost(@NotNull FocusEvent e) { setBorder(UNFOCUSED_BORDER); } }); @@ -138,7 +145,7 @@ abstract class FilterPopupComponent extends JPanel private void showPopupMenuFromKeyboard() { addKeyListener(new KeyAdapter() { @Override - public void keyPressed(KeyEvent e) { + public void keyPressed(@NotNull KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_DOWN) { showPopupMenu(); } @@ -159,12 +166,12 @@ abstract class FilterPopupComponent extends JPanel private void indicateHovering() { addMouseListener(new MouseAdapter() { @Override - public void mouseEntered(MouseEvent e) { + public void mouseEntered(@NotNull MouseEvent e) { setOnHoverForeground(); } @Override - public void mouseExited(MouseEvent e) { + public void mouseExited(@NotNull MouseEvent e) { setDefaultForeground(); } }); @@ -188,28 +195,15 @@ abstract class FilterPopupComponent extends JPanel popup.showUnderneathOf(this); } - private static class AllAction extends SetValueAction { - AllAction(@NotNull FilterPopupComponent component) { - super(ALL, component); - } - } + private class AllAction extends DumbAwareAction { - protected static class SetValueAction extends DumbAwareAction { - - private final String myValue; - private final FilterPopupComponent myFilterComponent; - - SetValueAction(String value, FilterPopupComponent filterComponent) { - super(value); - myValue = value; - myFilterComponent = filterComponent; + AllAction() { + super(ALL); } @Override - public void actionPerformed(AnActionEvent e) { - myFilterComponent.setValue(myValue); - myFilterComponent.applyFilters(); + public void actionPerformed(@NotNull AnActionEvent e) { + myFilterModel.setFilter(null); } } - } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultilinePopupBuilder.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultilinePopupBuilder.java index eab6cbc6f444..24a8abc7c52d 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultilinePopupBuilder.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultilinePopupBuilder.java @@ -84,7 +84,7 @@ class MultilinePopupBuilder { popup.setMinimumSize(new Dimension(200, 90)); AnAction okAction = new DumbAwareAction() { @Override - public void actionPerformed(AnActionEvent e) { + public void actionPerformed(@NotNull AnActionEvent e) { unregisterCustomShortcutSet(popup.getContent()); popup.closeOk(e.getInputEvent()); } @@ -131,6 +131,7 @@ class MultilinePopupBuilder { return lastPosition; } + @SuppressWarnings("StringToUpperCaseOrToLowerCaseWithoutLocale") @Override protected void addCompletionVariants(@NotNull String text, int offset, @NotNull String prefix, @NotNull CompletionResultSet result) { diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultipleValueFilterPopupComponent.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultipleValueFilterPopupComponent.java index 40bb39d2ec83..2f5b520d698e 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultipleValueFilterPopupComponent.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultipleValueFilterPopupComponent.java @@ -26,6 +26,7 @@ import com.intellij.openapi.ui.popup.JBPopupAdapter; import com.intellij.openapi.ui.popup.LightweightWindowEvent; import com.intellij.openapi.util.text.StringUtil; import com.intellij.vcs.log.VcsLogFilter; +import com.intellij.vcs.log.data.VcsLogUiProperties; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -36,12 +37,18 @@ abstract class MultipleValueFilterPopupComponent ex private static final int MAX_FILTER_VALUE_LENGTH = 30; - @Nullable private Collection mySelectedValues; + @NotNull protected final VcsLogUiProperties myUiProperties; - MultipleValueFilterPopupComponent(@NotNull VcsLogClassicFilterUi filterUi, @NotNull String filterName) { - super(filterUi, filterName); + MultipleValueFilterPopupComponent(@NotNull String filterName, + @NotNull VcsLogUiProperties uiProperties, + @NotNull FilterModel filterModel) { + super(filterName, filterModel); + myUiProperties = uiProperties; } + @NotNull + protected abstract Collection getValues(@Nullable Filter filter); + @NotNull protected abstract List> getRecentValuesFromSettings(); @@ -50,10 +57,8 @@ abstract class MultipleValueFilterPopupComponent ex @NotNull protected abstract List getAllValues(); - @Nullable - protected Collection getSelectedValues() { - return mySelectedValues; - } + @NotNull + protected abstract Filter createFilter(@NotNull Collection values); @NotNull protected ActionGroup createRecentItemsActionGroup() { @@ -69,15 +74,6 @@ abstract class MultipleValueFilterPopupComponent ex return group; } - void apply(@Nullable Collection values, @NotNull String text, @NotNull String tooltip) { - mySelectedValues = values; - applyFilters(); - setValue(text, tooltip); - if (values != null) { - rememberValuesInSettings(values); - } - } - @NotNull static String displayableText(@NotNull Collection values) { if (values.size() == 1) { @@ -91,17 +87,6 @@ abstract class MultipleValueFilterPopupComponent ex return StringUtil.join(values, ", "); } - @Override - @NotNull - protected AnAction createAllAction() { - return new DumbAwareAction(ALL) { - @Override - public void actionPerformed(AnActionEvent e) { - apply(null, ALL, ALL); - } - }; - } - @NotNull protected AnAction createPredefinedValueAction(@NotNull Collection values) { return new PredefinedValueAction(values); @@ -122,8 +107,9 @@ abstract class MultipleValueFilterPopupComponent ex } @Override - public void actionPerformed(AnActionEvent e) { - apply(myValues, displayableText(myValues), tooltip(myValues)); + public void actionPerformed(@NotNull AnActionEvent e) { + myFilterModel.setFilter(createFilter(myValues)); + rememberValuesInSettings(myValues); } } @@ -137,13 +123,14 @@ abstract class MultipleValueFilterPopupComponent ex } @Override - public void actionPerformed(AnActionEvent e) { + public void actionPerformed(@NotNull AnActionEvent e) { Project project = e.getProject(); if (project == null) { return; } - final MultilinePopupBuilder popupBuilder = new MultilinePopupBuilder(project, myVariants, getPopupText(mySelectedValues)); + Filter filter = myFilterModel.getFilter(); + final MultilinePopupBuilder popupBuilder = new MultilinePopupBuilder(project, myVariants, getPopupText(getValues(filter))); JBPopup popup = popupBuilder.createPopup(); popup.addListener(new JBPopupAdapter() { @Override @@ -151,10 +138,11 @@ abstract class MultipleValueFilterPopupComponent ex if (event.isOk()) { Collection selectedValues = popupBuilder.getSelectedValues(); if (selectedValues.isEmpty()) { - apply(null, ALL, ALL); + myFilterModel.setFilter(null); } else { - apply(selectedValues, displayableText(selectedValues), tooltip(selectedValues)); + myFilterModel.setFilter(createFilter(selectedValues)); + rememberValuesInSettings(selectedValues); } } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java index 3140d647795f..b4d90e8ca94a 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java @@ -17,6 +17,7 @@ package com.intellij.vcs.log.ui.filter; import com.intellij.openapi.actionSystem.ActionGroup; import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; @@ -24,26 +25,52 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; +import com.intellij.vcs.log.VcsLogDataPack; import com.intellij.vcs.log.VcsLogStructureFilter; import com.intellij.vcs.log.data.VcsLogStructureFilterImpl; import com.intellij.vcs.log.ui.VcsStructureChooser; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.*; class StructureFilterPopupComponent extends FilterPopupComponent { - public static final int FILTER_LABEL_LENGTH = 20; - @NotNull private final Collection myRoots; - @NotNull private final Collection myFiles; + private static final int FILTER_LABEL_LENGTH = 20; - public StructureFilterPopupComponent(@NotNull VcsLogClassicFilterUi filterUi, @NotNull Collection roots) { - super(filterUi, "Structure"); - myRoots = roots; - myFiles = ContainerUtil.newArrayList(); + public StructureFilterPopupComponent(@NotNull FilterModel filterModel) { + super("Structure", filterModel); + } + + @NotNull + @Override + protected String getText(@NotNull VcsLogStructureFilter filter) { + Collection files = getAllFiles(myFilterModel.getDataPack(), filter); + if (files.size() == 0) { + return ALL; + } + else if (files.size() == 1) { + VirtualFile file = files.iterator().next(); + return StringUtil.shortenPathWithEllipsis(file.getPresentableUrl(), FILTER_LABEL_LENGTH); + } + else { + return files.size() + " items"; + } + } + + @Nullable + @Override + protected String getToolTip(@NotNull VcsLogStructureFilter filter) { + return getToolTip(getAllFiles(myFilterModel.getDataPack(), filter)); + } + + @NotNull + private static Collection getAllFiles(@NotNull VcsLogDataPack dataPack, @NotNull VcsLogStructureFilter filter) { + Collection result = ContainerUtil.newArrayList(); + for (VirtualFile root : dataPack.getLogProviders().keySet()) { + result.addAll(filter.getFiles(root)); + } + return result; } @Override @@ -51,27 +78,8 @@ class StructureFilterPopupComponent extends FilterPopupComponent files) { - if (files.size() == 0) { - setValue(ALL); - } - else if (files.size() == 1) { - VirtualFile file = files.iterator().next(); - setValue(StringUtil.shortenPathWithEllipsis(file.getPresentableUrl(), FILTER_LABEL_LENGTH), getTooltip(files)); - } - else { - setValue(files.size() + " items", getTooltip(files)); - } - } - @NotNull - private static String getTooltip(@NotNull Collection files) { + private static String getToolTip(@NotNull Collection files) { List filesToDisplay = new ArrayList(files); if (files.size() > 10) { filesToDisplay = filesToDisplay.subList(0, 10); @@ -94,19 +102,17 @@ class StructureFilterPopupComponent extends FilterPopupComponent(myRoots)); + public void actionPerformed(@NotNull AnActionEvent e) { + Project project = e.getRequiredData(CommonDataKeys.PROJECT); + VcsLogDataPack dataPack = myFilterModel.getDataPack(); + VcsLogStructureFilter filter = myFilterModel.getFilter(); + Collection files = filter == null ? Collections.emptySet() : getAllFiles(dataPack, filter); + Set roots = dataPack.getLogProviders().keySet(); + VcsStructureChooser chooser = new VcsStructureChooser(project, "Select Files or Folders to Filter", files, + new ArrayList(roots)); if (chooser.showAndGet()) { - myFiles.clear(); - myFiles.addAll(chooser.getSelectedFiles()); - setValue(myFiles); - applyFilters(); + myFilterModel.setFilter(new VcsLogStructureFilterImpl(chooser.getSelectedFiles(), roots)); } } - } - } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/UserFilterPopupComponent.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/UserFilterPopupComponent.java index 368b426efc10..8451e549c7fc 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/UserFilterPopupComponent.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/UserFilterPopupComponent.java @@ -38,16 +38,29 @@ class UserFilterPopupComponent extends MultipleValueFilterPopupComponent filterModel) { + super("User", uiProperties, filterModel); myDataHolder = dataHolder; myUiProperties = uiProperties; } + @NotNull + @Override + protected String getText(@NotNull VcsLogUserFilter filter) { + return displayableText(getValues(filter)); + } + + @Nullable + @Override + protected String getToolTip(@NotNull VcsLogUserFilter filter) { + return tooltip(getValues(filter)); + } + @Override protected ActionGroup createActionGroup() { DefaultActionGroup group = new DefaultActionGroup(); @@ -62,17 +75,21 @@ class UserFilterPopupComponent extends MultipleValueFilterPopupComponent> getRecentValuesFromSettings() { - return myUiProperties.getRecentlyFilteredUserGroups(); + protected Collection getValues(@Nullable VcsLogUserFilter filter) { + if (filter == null) { + return Collections.emptySet(); + } + Set result = ContainerUtil.newHashSet(); + for (VirtualFile root : myFilterModel.getDataPack().getLogProviders().keySet()) { + result.addAll(filter.getUserNames(root)); + } + return result; } - @Nullable + @NotNull @Override - protected VcsLogUserFilter getFilter() { - if (getSelectedValues() == null) { - return null; - } - return new VcsLogUserFilterImpl(getSelectedValues(), myDataHolder.getCurrentUser()); + protected List> getRecentValuesFromSettings() { + return myUiProperties.getRecentlyFilteredUserGroups(); } @Override @@ -91,6 +108,12 @@ class UserFilterPopupComponent extends MultipleValueFilterPopupComponent values) { + return new VcsLogUserFilterImpl(values, myDataHolder.getCurrentUser()); + } + private static class VcsLogUserFilterImpl implements VcsLogUserFilter { @NotNull private final Collection myUsers; @@ -119,6 +142,7 @@ class UserFilterPopupComponent extends MultipleValueFilterPopupComponent() { + @SuppressWarnings("StringToUpperCaseOrToLowerCaseWithoutLocale") @Override public boolean value(String user) { String lowerUser = user.toLowerCase(); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/VcsLogClassicFilterUi.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/VcsLogClassicFilterUi.java index 8bc71682ad70..76b3f091eb8c 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/VcsLogClassicFilterUi.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/VcsLogClassicFilterUi.java @@ -20,11 +20,12 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.actionSystem.ex.CustomComponentAction; -import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.NotNullComputable; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.ui.SearchTextField; import com.intellij.ui.SearchTextFieldWithStoredHistory; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; @@ -35,91 +36,128 @@ import com.intellij.vcs.log.impl.VcsLogFilterCollectionImpl; import com.intellij.vcs.log.impl.VcsLogHashFilterImpl; import com.intellij.vcs.log.ui.VcsLogUiImpl; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.Arrays; -import java.util.Collection; import java.util.List; /** */ public class VcsLogClassicFilterUi implements VcsLogFilterUi { - private static final Logger LOG = Logger.getInstance(VcsLogClassicFilterUi.class); private static final String HASH_PATTERN = "[a-fA-F0-9]{7,}"; - @NotNull private final SearchTextField myTextFilter; @NotNull private final VcsLogUiImpl myUi; - @NotNull private final DefaultActionGroup myActionGroup; - @NotNull private final BranchFilterPopupComponent myBranchFilterComponent; - @NotNull private final UserFilterPopupComponent myUserFilterComponent; - @NotNull private final DateFilterPopupComponent myDateFilterComponent; - @NotNull private final StructureFilterPopupComponent myStructureFilterComponent; + @NotNull private final VcsLogDataHolder myLogDataHolder; + @NotNull private final VcsLogUiProperties myUiProperties; - public VcsLogClassicFilterUi(@NotNull VcsLogUiImpl ui, @NotNull VcsLogDataHolder logDataHolder, @NotNull VcsLogUiProperties uiProperties, + @NotNull private VcsLogDataPack myDataPack; + + @NotNull private final FilterModel myBranchFilterModel; + @NotNull private final FilterModel myUserFilterModel; + @NotNull private final FilterModel myDateFilterModel; + @NotNull private final FilterModel myStructureFilterModel; + @NotNull private final FilterModel myTextFilterModel; + + public VcsLogClassicFilterUi(@NotNull VcsLogUiImpl ui, + @NotNull VcsLogDataHolder logDataHolder, + @NotNull VcsLogUiProperties uiProperties, @NotNull VcsLogDataPack initialDataPack) { myUi = ui; + myLogDataHolder = logDataHolder; + myUiProperties = uiProperties; + myDataPack = initialDataPack; - myTextFilter = new SearchTextFieldWithStoredHistory("Vcs.Log.Text.Filter.History") { + NotNullComputable dataPackGetter = new NotNullComputable() { + @NotNull @Override - protected void onFieldCleared() { - applyFilters(); + public VcsLogDataPack compute() { + return myDataPack; } }; - myTextFilter.getTextEditor().addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - applyFilters(); - myTextFilter.addCurrentTextToHistory(); - } - }); + myBranchFilterModel = new FilterModel(dataPackGetter); + myUserFilterModel = new FilterModel(dataPackGetter); + myDateFilterModel = new FilterModel(dataPackGetter); + myStructureFilterModel = new FilterModel(dataPackGetter); + myTextFilterModel = new FilterModel(dataPackGetter); - myBranchFilterComponent = new BranchFilterPopupComponent(this, initialDataPack, uiProperties); - myUserFilterComponent = new UserFilterPopupComponent(this, logDataHolder, uiProperties); - myDateFilterComponent = new DateFilterPopupComponent(this); - myStructureFilterComponent = new StructureFilterPopupComponent(this, logDataHolder.getRoots()); + updateUiOnFilterChange(); + } - myActionGroup = new DefaultActionGroup(); - myActionGroup.add(new TextFilterComponent(myTextFilter)); - myActionGroup.add(new FilterActionComponent(myBranchFilterComponent)); - myActionGroup.add(new FilterActionComponent(myUserFilterComponent)); - myActionGroup.add(new FilterActionComponent(myDateFilterComponent)); - myActionGroup.add(new FilterActionComponent(myStructureFilterComponent)); + private void updateUiOnFilterChange() { + FilterModel[] models = {myBranchFilterModel, myUserFilterModel, myDateFilterModel, myStructureFilterModel, myTextFilterModel}; + for (FilterModel model : models) { + model.addSetFilterListener(new Runnable() { + @Override + public void run() { + myUi.applyFiltersAndUpdateUi(); + } + }); + } } public void updateDataPack(@NotNull VcsLogDataPack dataPack) { - myBranchFilterComponent.updateDataPack(dataPack); + myDataPack = dataPack; } /** * Returns filter components which will be added to the Log toolbar. */ @NotNull - public ActionGroup getActionGroup() { - return myActionGroup; - } - - @NotNull - public List getComponents() { - return Arrays.asList(myTextFilter.getTextEditor(), myBranchFilterComponent, myUserFilterComponent, - myDateFilterComponent, myStructureFilterComponent); + public ActionGroup createActionGroup() { + DefaultActionGroup actionGroup = new DefaultActionGroup(); + actionGroup.add(new TextFilterComponent(myTextFilterModel)); + actionGroup.add(new FilterActionComponent(new Computable() { + @Override + public JComponent compute() { + return new BranchFilterPopupComponent(myUiProperties, myBranchFilterModel); + } + })); + actionGroup.add(new FilterActionComponent(new Computable() { + @Override + public JComponent compute() { + return new UserFilterPopupComponent(myUiProperties, myLogDataHolder, myUserFilterModel); + } + })); + actionGroup.add(new FilterActionComponent(new Computable() { + @Override + public JComponent compute() { + return new DateFilterPopupComponent(myDateFilterModel); + } + })); + actionGroup.add(new FilterActionComponent(new Computable() { + @Override + public JComponent compute() { + return new StructureFilterPopupComponent(myStructureFilterModel); + } + })); + return actionGroup; } @NotNull @Override public VcsLogFilterCollection getFilters() { - Pair filtersFromText = getFiltersFromTextArea(myTextFilter.getText().trim()); - return new VcsLogFilterCollectionImpl(myBranchFilterComponent.getFilter(), myUserFilterComponent.getFilter(), - filtersFromText.second, myDateFilterComponent.getFilter(), - filtersFromText.first, myStructureFilterComponent.getFilter()); + ApplicationManager.getApplication().assertIsDispatchThread(); + Pair filtersFromText = getFiltersFromTextArea(myTextFilterModel.getFilter()); + return new VcsLogFilterCollectionImpl(myBranchFilterModel.getFilter(), + myUserFilterModel.getFilter(), + filtersFromText.second, + myDateFilterModel.getFilter(), + filtersFromText.first, + myStructureFilterModel.getFilter()); } @NotNull - private static Pair getFiltersFromTextArea(@NotNull String text) { - if (text.isEmpty()) { + private static Pair getFiltersFromTextArea(@Nullable VcsLogTextFilter filter) { + if (filter == null) { + return Pair.empty(); + } + String text = filter.getText().trim(); + if (StringUtil.isEmptyOrSpaces(text)) { return Pair.empty(); } List hashes = ContainerUtil.newArrayList(); @@ -147,23 +185,21 @@ public class VcsLogClassicFilterUi implements VcsLogFilterUi { @Override public void setFilter(@NotNull VcsLogFilter filter) { + ApplicationManager.getApplication().assertIsDispatchThread(); if (filter instanceof VcsLogBranchFilter) { - Collection values = ((VcsLogBranchFilter)filter).getBranchNames(); - myBranchFilterComponent.apply(values, MultipleValueFilterPopupComponent.displayableText(values), - MultipleValueFilterPopupComponent.tooltip(values)); + myBranchFilterModel.setFilter((VcsLogBranchFilter)filter); + JComponent toolbar = myUi.getMainFrame().getToolbar(); + toolbar.revalidate(); + toolbar.repaint(); } } - void applyFilters() { - myUi.applyFiltersAndUpdateUi(); - } - private static class TextFilterComponent extends DumbAwareAction implements CustomComponentAction { - private final SearchTextField mySearchField; + private final FilterModel myFilterModel; - TextFilterComponent(SearchTextField searchField) { - mySearchField = searchField; + public TextFilterComponent(FilterModel filterModel) { + myFilterModel = filterModel; } @Override @@ -172,30 +208,47 @@ public class VcsLogClassicFilterUi implements VcsLogFilterUi { JLabel filterCaption = new JLabel("Filter:"); filterCaption.setForeground(UIUtil.isUnderDarcula() ? UIUtil.getLabelForeground() : UIUtil.getInactiveTextColor()); panel.add(filterCaption); - panel.add(mySearchField); + panel.add(createSearchField()); return panel; } + private Component createSearchField() { + final SearchTextFieldWithStoredHistory textFilter = new SearchTextFieldWithStoredHistory("Vcs.Log.Text.Filter.History") { + @Override + protected void onFieldCleared() { + myFilterModel.setFilter(null); + } + }; + textFilter.getTextEditor().addActionListener(new ActionListener() { + @Override + public void actionPerformed(@NotNull ActionEvent e) { + myFilterModel.setFilter(new VcsLogTextFilterImpl(textFilter.getText())); + textFilter.addCurrentTextToHistory(); + } + }); + return textFilter; + } + @Override - public void actionPerformed(AnActionEvent e) { + public void actionPerformed(@NotNull AnActionEvent e) { } } private static class FilterActionComponent extends DumbAwareAction implements CustomComponentAction { - private final FilterPopupComponent myComponent; - public FilterActionComponent(FilterPopupComponent component) { - myComponent = component; + @NotNull private final Computable myComponentCreator; + + public FilterActionComponent(@NotNull Computable componentCreator) { + myComponentCreator = componentCreator; } @Override public JComponent createCustomComponent(Presentation presentation) { - return myComponent; + return myComponentCreator.compute(); } @Override - public void actionPerformed(AnActionEvent e) { + public void actionPerformed(@NotNull AnActionEvent e) { } } - } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java index 45f05ad6646a..484e7c4b4ad3 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java @@ -215,7 +215,7 @@ public class MainFrame extends JPanel implements TypeSafeDataProvider { toolbarGroup.add(ActionManager.getInstance().getAction(VcsLogUiImpl.TOOLBAR_ACTION_GROUP)); DefaultActionGroup mainGroup = new DefaultActionGroup(); - mainGroup.add(myFilterUi.getActionGroup()); + mainGroup.add(myFilterUi.createActionGroup()); mainGroup.addSeparator(); mainGroup.add(toolbarGroup); ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.CHANGES_VIEW_TOOLBAR, mainGroup, true); @@ -264,7 +264,8 @@ public class MainFrame extends JPanel implements TypeSafeDataProvider { } } - public Component getToolbar() { + @NotNull + public JComponent getToolbar() { return myToolbar; } @@ -382,8 +383,7 @@ public class MainFrame extends JPanel implements TypeSafeDataProvider { @NotNull @Override protected List getOrderedComponents() { - return ContainerUtil.concat(Arrays.asList(myGraphTable, myChangesBrowser.getPreferredFocusedComponent()), - myFilterUi.getComponents()); + return Arrays.asList(myGraphTable, myChangesBrowser.getPreferredFocusedComponent()); } } From fbb16cf0fcb19c9df894de5acf448f6cf04afc87 Mon Sep 17 00:00:00 2001 From: Alexander Marchuk Date: Sun, 16 Nov 2014 18:59:55 +0300 Subject: [PATCH 2/3] move java logic to python side load meta data for array in first request --- python/helpers/pydev/pydev_console_utils.py | 8 +- python/helpers/pydev/pydevd_comm.py | 20 +- python/helpers/pydev/pydevd_vars.py | 99 +++- .../jetbrains/python/debugger/ArrayChunk.java | 103 ++++ .../python/debugger/PyFrameAccessor.java | 2 +- .../debugger/pydev/GetArrayCommand.java | 9 +- .../debugger/pydev/MultiProcessDebugger.java | 2 +- .../debugger/pydev/ProcessDebugger.java | 7 +- .../python/debugger/pydev/ProtocolParser.java | 17 +- .../python/debugger/pydev/RemoteDebugger.java | 2 +- .../console/PydevConsoleCommunication.java | 11 +- .../python/debugger/PyDebugProcess.java | 2 +- .../debugger/array/AsyncArrayTableModel.java | 34 +- .../debugger/array/NumpyArrayTable.java | 560 +++++------------- 14 files changed, 398 insertions(+), 478 deletions(-) create mode 100644 python/pydevSrc/com/jetbrains/python/debugger/ArrayChunk.java diff --git a/python/helpers/pydev/pydev_console_utils.py b/python/helpers/pydev/pydev_console_utils.py index 2980873cd323..d12419e19e57 100644 --- a/python/helpers/pydev/pydev_console_utils.py +++ b/python/helpers/pydev/pydev_console_utils.py @@ -387,9 +387,13 @@ class BaseInterpreterInterface: def getArray(self, attr, roffset, coffset, rows, cols, format): xml = "" - array = pydevd_vars.evalInContext(attr, self.getNamespace(), self.getNamespace()) + name = ".".join(attr.split("\t")) + array = pydevd_vars.evalInContext(name, self.getNamespace(), self.getNamespace()) - xml += pydevd_vars.array_to_xml(array, roffset, coffset, rows, cols, format) + if rows == -1 and cols == -1: + xml += pydevd_vars.array_to_meta_xml(array, name, format) + else: + xml += pydevd_vars.array_to_xml(array, roffset, coffset, rows, cols, format) xml += "" diff --git a/python/helpers/pydev/pydevd_comm.py b/python/helpers/pydev/pydevd_comm.py index 5727b2d8f790..8b7596d5818c 100644 --- a/python/helpers/pydev/pydevd_comm.py +++ b/python/helpers/pydev/pydevd_comm.py @@ -974,27 +974,33 @@ class InternalGetArray(InternalThreadCommand): self.thread_id = thread_id self.frame_id = frame_id self.scope = scope - self.name = attrs[-1] - self.attrs = attrs; + self.name = ".".join(attrs.split("\t")) + self.attrs = attrs self.roffset = int(roffset) self.coffset = int(coffset) self.rows = int(rows) self.cols = int(cols) - self.format = '\'' + format + '\'' + self.format = format + if hasattr(self.format, 'decode'): + self.format = self.format.decode('utf-8') def doIt(self, dbg): try: - var = getVariable(self.thread_id, self.frame_id, 'EXPRESSION', self.attrs) + frame = pydevd_vars.findFrame(self.thread_id, self.frame_id) + var = pydevd_vars.evalInContext(self.name, frame.f_globals, frame.f_locals) xml = "" - xml += pydevd_vars.array_to_xml(var, self.roffset, self.coffset, self.rows, self.cols, self.format) - + # need metadata + if self.rows == -1 and self.cols == -1: + xml += pydevd_vars.array_to_meta_xml(var, self.name, self.format) + else: + xml += pydevd_vars.array_to_xml(var, self.roffset, self.coffset, self.rows, self.cols, self.format) xml += "" cmd = dbg.cmdFactory.makeGetArrayMessage(self.sequence, xml) dbg.writer.addCommand(cmd) except: - cmd = dbg.cmdFactory.makeErrorMessage(self.sequence, "Error resolving array " + GetExceptionTracebackStr()) + cmd = dbg.cmdFactory.makeErrorMessage(self.sequence, "Error resolving array: " + GetExceptionTracebackStr()) dbg.writer.addCommand(cmd) #======================================================================================================================= diff --git a/python/helpers/pydev/pydevd_vars.py b/python/helpers/pydev/pydevd_vars.py index db297f991928..b03560345844 100644 --- a/python/helpers/pydev/pydevd_vars.py +++ b/python/helpers/pydev/pydevd_vars.py @@ -388,44 +388,45 @@ def changeAttrExpression(thread_id, frame_id, attr, expression, dbg): except Exception: traceback.print_exc() -MAXIMUM_ARRAY_SIZE = 300 +MAXIMUM_ARRAY_SIZE = 100 +MAX_SLICE_SIZE = 1000 def array_to_xml(array, roffset, coffset, rows, cols, format): xml = "" rows = min(rows, MAXIMUM_ARRAY_SIZE) cols = min(cols, MAXIMUM_ARRAY_SIZE) - if rows == 1 and cols == 1: - rows = 1 - cols = 1 - elif rows == 1 or cols == 1: - is_row = True if (rows == 1) else False - if is_row: - array = array[roffset:] - else: - array = array[coffset:] - if len(array) == 1: - array = array[0] - if is_row: - cols = min(cols, len(array)) - else: - rows = min(rows, len(array)) - else: + #there is no obvious rule for slicing (at least 5 choices) + if len(array) == 1 and (rows > 1 or cols > 1): + array = array[0] + if array.size > len(array): array = array[roffset:, coffset:] rows = min(rows, len(array)) cols = min(cols, len(array[0])) - xml += "" % (rows, cols) + if len(array) == 1: + array = array[0] + elif array.size == len(array): + if roffset == 0 and rows == 1: + array = array[coffset:] + cols = min(cols, len(array)) + elif coffset == 0 and cols == 1: + array = array[roffset:] + rows = min(rows, len(array)) + + xml += "" % (False, rows, cols) for row in range(rows): xml += "" % to_string(row) for col in range(cols): value = array if rows == 1 or cols == 1: if rows == 1 and cols == 1: - value = array + value = array[0] else: dim = col if (rows == 1) else row value = array[dim] + if "ndarray" in str(type(value)): + value = value[0] else: value = array[row][col] value = format % value @@ -433,6 +434,66 @@ def array_to_xml(array, roffset, coffset, rows, cols, format): return xml +def array_to_meta_xml(array, name, format): + type = array.dtype.kind + slice = name + l = len(array.shape) + + # initial load, compute slice + if format == '%': + if l > 2: + slice += '[0]' * (l - 2) + for r in range(l - 2): + array = array[0] + if type == 'f': + format = '.5f' + elif type == 'i' or type == 'u': + format = 'd' + else: + format = 's' + else: + format = format.replace('%', '') + + l = len(array.shape) + reslice = "" + if l > 2: + raise Exception("%s has more than 2 dimensions." % slice) + elif l == 1: + # special case with 1D arrays arr[i, :] - row, but arr[:, i] - column with equal shape and ndim + # http://stackoverflow.com/questions/16837946/numpy-a-2-rows-1-column-file-loadtxt-returns-1row-2-columns + # explanation: http://stackoverflow.com/questions/15165170/how-do-i-maintain-row-column-orientation-of-vectors-in-numpy?rq=1 + # we use kind of a hack - get information about memory from C_CONTIGUOUS + is_row = array.flags['C_CONTIGUOUS'] + + if is_row: + rows = 1 + cols = min(len(array), MAX_SLICE_SIZE) + if cols < len(array): + reslice = '[0:%s]' % (cols) + array = array[0:cols] + else: + cols = 1 + rows = min(len(array), MAX_SLICE_SIZE) + if rows < len(array): + reslice = '[0:%s]' % (rows) + array = array[0:rows] + elif l == 2: + rows = min(array.shape[-2], MAX_SLICE_SIZE) + cols = min(array.shape[-1], MAX_SLICE_SIZE) + if cols < array.shape[-1] or rows < array.shape[-2]: + reslice = '[0:%s, 0:%s]' % (rows, cols) + array = array[0:rows, 0:cols] + + #avoid slice duplication + if not slice.endswith(reslice): + slice += reslice + + bounds = (0, 0) + if type in "biufc": + bounds = (array.min(), array.max()) + xml = '' % \ + (True, slice, rows, cols, format, type, bounds[1], bounds[0]) + return xml diff --git a/python/pydevSrc/com/jetbrains/python/debugger/ArrayChunk.java b/python/pydevSrc/com/jetbrains/python/debugger/ArrayChunk.java new file mode 100644 index 000000000000..d7e5ade2895a --- /dev/null +++ b/python/pydevSrc/com/jetbrains/python/debugger/ArrayChunk.java @@ -0,0 +1,103 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.debugger; + +import com.jetbrains.python.debugger.PyDebugValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author amarch + */ +public class ArrayChunk { + private final PyDebugValue myValue; + private final String mySlicePresentation; + private final int myRows; + private final int myColumns; + private final String myMax; + private final String myMin; + private final String myFormat; + private final String myType; + private final Object[][] myData; + + public ArrayChunk(@NotNull PyDebugValue value, + String slicePresentation, + int rows, + int columns, + String max, + String min, + String format, + String type, + @Nullable Object[][] data) { + myValue = value; + mySlicePresentation = slicePresentation; + myRows = rows; + myColumns = columns; + myMax = max; + myMin = min; + myFormat = format; + myType = type; + myData = data; + } + + public PyDebugValue getValue() { + return myValue; + } + + public String getSlicePresentation() { + return mySlicePresentation; + } + + public int getRows() { + return myRows; + } + + public int getColumns() { + return myColumns; + } + + public String getMax() { + return myMax; + } + + public String getMin() { + return myMin; + } + + public String getFormat() { + return myFormat; + } + + public String getType() { + return myType; + } + + public Object[][] getData() { + return myData; + } + + public boolean metaOnly() { + return myData == null; + } + + public boolean containsMeta() { + return !mySlicePresentation.isEmpty() && + myRows > 0 && + myColumns > 0 && + !myFormat.isEmpty() && + !myType.isEmpty(); + } +} diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java b/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java index e4f68b797571..0e88aef89a76 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java @@ -21,5 +21,5 @@ public interface PyFrameAccessor { @Nullable PyReferrersLoader getReferrersLoader(); - Object[][] getArrayItems(PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) throws PyDebuggerException; + ArrayChunk getArrayItems(PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) throws PyDebuggerException; } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetArrayCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetArrayCommand.java index cfb244759eb6..da19bf2381a6 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetArrayCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetArrayCommand.java @@ -1,5 +1,6 @@ package com.jetbrains.python.debugger.pydev; +import com.jetbrains.python.debugger.ArrayChunk; import com.jetbrains.python.debugger.PyDebugValue; import com.jetbrains.python.debugger.PyDebuggerException; @@ -15,7 +16,7 @@ public class GetArrayCommand extends GetFrameCommand { private final int myRows; private final int myColumns; private final String myFormat; - private Object[][] myArrayItems; + private ArrayChunk myChunk; public GetArrayCommand(final RemoteDebugger debugger, final String threadId, final String frameId, PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) { super(debugger, GET_ARRAY, threadId, frameId); @@ -54,10 +55,10 @@ public class GetArrayCommand extends GetFrameCommand { if (response.getCommand() >= 900 && response.getCommand() < 1000) { throw new PyDebuggerException(response.getPayload()); } - myArrayItems = ProtocolParser.parseArrayValues(response.getPayload(), myDebugProcess); + myChunk = ProtocolParser.parseArrayValues(response.getPayload(), myDebugProcess); } - public Object[][] getArray(){ - return myArrayItems; + public ArrayChunk getArray(){ + return myChunk; } } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java index 21b9d8be2867..b864740d2ad0 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java @@ -179,7 +179,7 @@ public class MultiProcessDebugger implements ProcessDebugger { return debugger(threadId).loadVariable(threadId, frameId, var); } - public Object[][] loadArrayItems(String threadId, + public ArrayChunk loadArrayItems(String threadId, String frameId, PyDebugValue var, int rowOffset, diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java index de1296b9a4d5..5c9deabfcc5f 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java @@ -2,10 +2,7 @@ package com.jetbrains.python.debugger.pydev; import com.intellij.xdebugger.frame.XValueChildrenList; import com.jetbrains.python.console.pydev.PydevCompletionVariant; -import com.jetbrains.python.debugger.PyDebugValue; -import com.jetbrains.python.debugger.PyDebuggerException; -import com.jetbrains.python.debugger.PyReferringObjectsValue; -import com.jetbrains.python.debugger.PyThreadInfo; +import com.jetbrains.python.debugger.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -36,7 +33,7 @@ public interface ProcessDebugger { // todo: don't generate temp variables for qualified expressions - just split 'em XValueChildrenList loadVariable(String threadId, String frameId, PyDebugValue var) throws PyDebuggerException; - Object[][] loadArrayItems(String threadId, String frameId, PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) throws PyDebuggerException; + ArrayChunk loadArrayItems(String threadId, String frameId, PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) throws PyDebuggerException; void loadReferrers(String threadId, String frameId, PyReferringObjectsValue var, PyDebugCallback callback); diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java index e47f4f70ad00..42f17bd042f0 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java @@ -179,7 +179,7 @@ public class ProtocolParser { return new PyDebugValue(name, type, value, "True".equals(isContainer), "True".equals(isErrorOnEval), frameAccessor); } - public static Object[][] parseArrayValues(final String text, final PyFrameAccessor frameAccessor) throws PyDebuggerException { + public static ArrayChunk parseArrayValues(final String text, final PyFrameAccessor frameAccessor) throws PyDebuggerException { final XppReader reader = openReader(text, false); int cols = 0; int rows = 0; @@ -189,6 +189,17 @@ public class ProtocolParser { if (!"array".equals(reader.getNodeName())) { throw new PyDebuggerException("Expected at first node, found " + reader.getNodeName()); } + boolean meta = readString(reader, "meta", null).equals("True"); + if (meta) { + String slice = readString(reader, "slice", null); + rows = readInt(reader, "rows", null); + cols = readInt(reader, "cols", null); + String format = "%" + readString(reader, "format", null); + String type = readString(reader, "type", null); + String max = readString(reader, "max", null); + String min = readString(reader, "min", null); + return new ArrayChunk(new PyDebugValue(slice, null, null, false, false, frameAccessor), slice, rows, cols, max, min, format, type, null); + } rows = readInt(reader, "rows", null); cols = readInt(reader, "cols", null); reader.moveUp(); @@ -197,7 +208,7 @@ public class ProtocolParser { return parseArrayValues(reader, frameAccessor, cols, rows); } - public static Object[][] parseArrayValues(final XppReader reader, final PyFrameAccessor frameAccessor, final int cols, final int rows) throws PyDebuggerException { + public static ArrayChunk parseArrayValues(final XppReader reader, final PyFrameAccessor frameAccessor, final int cols, final int rows) throws PyDebuggerException { if (rows <= 0 || cols <= 0) { throw new PyDebuggerException("Array xml: bad rows or columns number: (" + rows + ", " + cols + ")"); } @@ -228,7 +239,7 @@ public class ProtocolParser { reader.moveUp(); } - return values; + return new ArrayChunk(new PyDebugValue("", null, null, false, false, frameAccessor), "", rows, cols, null, null, null, null, values); } private static XppReader openReader(final String text, final boolean checkForContent) throws PyDebuggerException { diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java index 2fd0374ae545..11cc0d798e58 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java @@ -155,7 +155,7 @@ public class RemoteDebugger implements ProcessDebugger { } @Override - public Object[][] loadArrayItems(String threadId, String frameId, PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) throws PyDebuggerException { + public ArrayChunk loadArrayItems(String threadId, String frameId, PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) throws PyDebuggerException { final GetArrayCommand command = new GetArrayCommand(this, threadId, frameId, var, rowOffset, colOffset, rows, cols, format); command.execute(); return command.getArray(); diff --git a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java index c27cfbe0ca0a..5f6ea134e410 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java @@ -548,11 +548,18 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl } @Override - public Object[][] getArrayItems(PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) + public ArrayChunk getArrayItems(PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) throws PyDebuggerException { if (myClient != null) { try { - Object ret = myClient.execute(GET_ARRAY, new Object[]{var.getName(), rowOffset, colOffset, rows, cols, format}); + String fullName = var.getName(); + PyDebugValue child = var; + while (child.getParent() != null) { + child = child.getParent(); + fullName = child.getName() + "\t" + fullName; + } + + Object ret = myClient.execute(GET_ARRAY, new Object[]{fullName, rowOffset, colOffset, rows, cols, format}); if (ret instanceof String) { return ProtocolParser.parseArrayValues((String)ret, this); } diff --git a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java index 8842373ef2a9..c09b2b03e743 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java +++ b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java @@ -572,7 +572,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr } @Override - public Object[][] getArrayItems(PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) + public ArrayChunk getArrayItems(PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) throws PyDebuggerException { final PyStackFrame frame = currentFrame(); return myDebugger.loadArrayItems(frame.getThreadId(), frame.getFrameId(), var, rowOffset, colOffset, rows, cols, format); diff --git a/python/src/com/jetbrains/python/debugger/array/AsyncArrayTableModel.java b/python/src/com/jetbrains/python/debugger/array/AsyncArrayTableModel.java index 278bde5f3eb7..3eb47155d316 100644 --- a/python/src/com/jetbrains/python/debugger/array/AsyncArrayTableModel.java +++ b/python/src/com/jetbrains/python/debugger/array/AsyncArrayTableModel.java @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFutureTask; import com.intellij.openapi.util.Pair; import com.intellij.util.ui.UIUtil; +import com.jetbrains.python.debugger.ArrayChunk; import com.jetbrains.python.debugger.PyDebugValue; import javax.swing.table.AbstractTableModel; @@ -35,26 +36,26 @@ public class AsyncArrayTableModel extends AbstractTableModel { private static final int CHUNK_ROW_SIZE = 30; public static final String EMPTY_CELL_VALUE = ""; - private final int myRows; - private final int myColumns; + private int myRows; + private int myColumns; private final NumpyArrayTable myProvider; private final ExecutorService myExecutorService = Executors.newSingleThreadExecutor(); - private LoadingCache, ListenableFuture> myChunkCache = CacheBuilder.newBuilder().build( - new CacheLoader, ListenableFuture>() { + private LoadingCache, ListenableFuture> myChunkCache = CacheBuilder.newBuilder().build( + new CacheLoader, ListenableFuture>() { @Override - public ListenableFuture load(final Pair key) throws Exception { + public ListenableFuture load(final Pair key) throws Exception { final PyDebugValue value = myProvider.getDebugValue(); final PyDebugValue slicedValue = new PyDebugValue(myProvider.getSliceText(), value.getType(), value.getValue(), value.isContainer(), value.isErrorOnEval(), value.getFrameAccessor()); - ListenableFutureTask task = ListenableFutureTask.create(new Callable() { + ListenableFutureTask task = ListenableFutureTask.create(new Callable() { @Override - public Object[][] call() throws Exception { + public ArrayChunk call() throws Exception { return value.getFrameAccessor() .getArrayItems(slicedValue, key.first, key.second, Math.min(CHUNK_ROW_SIZE, getRowCount() - key.first), Math.min(CHUNK_COL_SIZE, getColumnCount() - key.second), @@ -91,15 +92,16 @@ public class AsyncArrayTableModel extends AbstractTableModel { Pair key = itemToChunkKey(row, col); try { - ListenableFuture chunk = myChunkCache.get(key); + ListenableFuture chunk = myChunkCache.get(key); if (chunk.isDone()) { + Object[][] data = chunk.get().getData(); int r = row % CHUNK_ROW_SIZE; int c = col % CHUNK_COL_SIZE; - if (r < chunk.get().length) { - if (c < chunk.get()[r].length) { - return myProvider.correctStringValue(chunk.get()[r][c]); + if (r < data.length) { + if (c < data[r].length) { + return myProvider.correctStringValue(data[r][c]); } } } @@ -149,17 +151,21 @@ public class AsyncArrayTableModel extends AbstractTableModel { } public void changeValue(int row, int col, Object value) { - Future chunk = myChunkCache.getIfPresent(itemToChunkKey(row, col)); + Future chunk = myChunkCache.getIfPresent(itemToChunkKey(row, col)); if (chunk != null && chunk.isDone()) { try { - chunk.get()[row - getPageRowStart(row)][col - getPageColStart(col)] = value; + chunk.get().getData()[row - getPageRowStart(row)][col - getPageColStart(col)] = value; } catch (Exception e) { throw new IllegalStateException(e); } } else { - throw new IllegalArgumentException("Forced to change empty cell in " + row + " row and " + col + "column."); + throw new IllegalArgumentException("Forced to change empty cell in " + row + " row and " + col + " column."); } } + + public void addToCache(ArrayChunk chunk) { + //TODO: add preloaded values + } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/debugger/array/NumpyArrayTable.java b/python/src/com/jetbrains/python/debugger/array/NumpyArrayTable.java index dacf3d51a770..6a2122e9b7f1 100644 --- a/python/src/com/jetbrains/python/debugger/array/NumpyArrayTable.java +++ b/python/src/com/jetbrains/python/debugger/array/NumpyArrayTable.java @@ -15,11 +15,12 @@ */ package com.jetbrains.python.debugger.array; +import com.google.common.util.concurrent.ListenableFutureTask; import com.intellij.codeInsight.hint.HintManager; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; @@ -27,10 +28,7 @@ import com.intellij.util.Consumer; import com.intellij.util.ui.UIUtil; import com.intellij.xdebugger.evaluation.XDebuggerEvaluator; import com.intellij.xdebugger.frame.XValue; -import com.jetbrains.python.debugger.PyDebugValue; -import com.jetbrains.python.debugger.PyDebuggerEvaluator; -import com.jetbrains.python.debugger.PyDebuggerException; -import com.jetbrains.python.debugger.PyFrameAccessor; +import com.jetbrains.python.debugger.*; import org.jetbrains.annotations.NotNull; import javax.management.InvalidAttributeValueException; @@ -40,6 +38,10 @@ import java.awt.*; import java.awt.event.*; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -54,7 +56,6 @@ public class NumpyArrayTable { private Project myProject; private PyDebuggerEvaluator myEvaluator; private String myDtypeKind; - private int[] myShape; private ArrayTableCellRenderer myTableCellRenderer; private AsyncArrayTableModel myPagingModel; @@ -81,7 +82,7 @@ public class NumpyArrayTable { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { - doReslice(getSliceText(), null); + doReslice(); } } }, new KeyAdapter() { @@ -101,57 +102,39 @@ public class NumpyArrayTable { return myComponent; } - private AsyncArrayTableModel createTableModel(@NotNull int[] shape) { - final int columns = Math.min(getMaxColumn(shape), COLUMNS_IN_DEFAULT_VIEW); - int rows = Math.min(getMaxRow(shape), ROWS_IN_DEFAULT_VIEW); - if (columns == 0 || rows == 0) { - showError("Slice with zero axis shape."); - } - - return new AsyncArrayTableModel(rows, columns, this); - } - private void initComponent() { - //add table renderer - myTableCellRenderer = new ArrayTableCellRenderer(Double.MIN_VALUE, Double.MIN_VALUE, myDtypeKind); + myComponent.getColoredCheckbox().setEnabled(false); + myComponent.getColoredCheckbox().addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getSource() == myComponent.getColoredCheckbox()) { - //add color checkbox listener - if (!isNumeric()) { - disableColor(); - } - else { - myComponent.getColoredCheckbox().addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - if (e.getSource() == myComponent.getColoredCheckbox()) { - - if (myTable.getRowCount() > 0 && - myTable.getColumnCount() > 0 && - myTable.getCellRenderer(0, 0) instanceof ArrayTableCellRenderer) { - ArrayTableCellRenderer renderer = (ArrayTableCellRenderer)myTable.getCellRenderer(0, 0); - if (myComponent.getColoredCheckbox().isSelected()) { - renderer.setColored(true); - } - else { - renderer.setColored(false); - } + if (myTable.getRowCount() > 0 && + myTable.getColumnCount() > 0 && + myTable.getCellRenderer(0, 0) instanceof ArrayTableCellRenderer) { + ArrayTableCellRenderer renderer = (ArrayTableCellRenderer)myTable.getCellRenderer(0, 0); + if (myComponent.getColoredCheckbox().isSelected()) { + renderer.setColored(true); + } + else { + renderer.setColored(false); } - myComponent.getScrollPane().repaint(); } + myComponent.getScrollPane().repaint(); } - }); - } + } + }); //make value name read-only myComponent.getSliceTextField().addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent e) { - myComponent.getSliceTextField().getDocument().createGuardedBlock(0, getNodeName().length()); + myComponent.getSliceTextField().getDocument().createGuardedBlock(0, getNodeFullName().length()); } @Override public void focusLost(FocusEvent e) { - RangeMarker block = myComponent.getSliceTextField().getDocument().getRangeGuard(0, getNodeName().length()); + RangeMarker block = myComponent.getSliceTextField().getDocument().getRangeGuard(0, getNodeFullName().length()); if (block != null) { myComponent.getSliceTextField().getDocument().removeGuardedBlock(block); } @@ -184,213 +167,130 @@ public class NumpyArrayTable { } public void init() { - Runnable returnToFillTable = new Runnable() { + init(myValue.getName(), false); + } + + public void init(final String slice, final boolean inPlace) { + initComponent(); + + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + + final ExecutorService myExecutorService = Executors.newSingleThreadExecutor(); + @Override public void run() { - init(); - } - }; + final PyDebugValue value = getDebugValue(); + PyDebugValue parent = value.getParent(); + String sl = slice; - if (myDtypeKind == null) { - fillType(returnToFillTable); - return; - } + if (slice.contains(".")) { + sl = slice.substring(slice.lastIndexOf(".") + 1); + } - if (myShape == null) { - fillShape(returnToFillTable); - return; - } + final PyDebugValue slicedValue = + new PyDebugValue(sl, value.getType(), value.getValue(), value.isContainer(), value.isErrorOnEval(), + parent, value.getFrameAccessor()); - if (myTableCellRenderer == null) { - initComponent(); - } + final String format = getFormat().isEmpty() ? "%" : getFormat(); - if (isNumeric() && myTableCellRenderer.getMax() == Double.MIN_VALUE && myTableCellRenderer.getMin() == Double.MIN_VALUE) { - fillColorRange(returnToFillTable); - return; - } + final ListenableFutureTask task = ListenableFutureTask.create(new Callable() { + @Override + public ArrayChunk call() throws Exception { + return value.getFrameAccessor() + .getArrayItems(slicedValue, 0, 0, -1, -1, format); + } + }); - UIUtil.invokeLaterIfNeeded(new Runnable() { - @Override - public void run() { - myComponent.getSliceTextField().setText(getDefaultPresentation()); - myComponent.getFormatTextField().setText(getDefaultFormat()); - myDialog.setTitle(getTitlePresentation(getDefaultPresentation())); + task.addListener(new Runnable() { + @Override + public void run() { + if (task.isDone()) { + try { + initUi(task.get(), inPlace); + } + catch (Exception e) { + showError(e.getMessage()); + } + } + } + }, myExecutorService); + + myExecutorService.execute(task); } }); - initTableModel(false); + } + + private void initUi(@NotNull final ArrayChunk chunk, final boolean inPlace) { + if (chunk.containsMeta()) { + myPagingModel = new AsyncArrayTableModel(Math.min(chunk.getRows(), ROWS_IN_DEFAULT_VIEW), + Math.min(chunk.getColumns(), COLUMNS_IN_DEFAULT_VIEW), this); + myPagingModel.addToCache(chunk); + myDtypeKind = chunk.getType(); + + UIUtil.invokeLaterIfNeeded(new Runnable() { + @Override + public void run() { + myTable.setModel(myPagingModel); + myComponent.getSliceTextField().setText(chunk.getSlicePresentation()); + myComponent.getFormatTextField().setText(chunk.getFormat()); + myDialog.setTitle(getTitlePresentation(chunk.getSlicePresentation())); + myTableCellRenderer = new ArrayTableCellRenderer(Double.MIN_VALUE, Double.MIN_VALUE, chunk.getType()); + fillColorRange(chunk.getMin(), chunk.getMax()); + if (!isNumeric()) { + disableColor(); + } + else { + myComponent.getColoredCheckbox().setEnabled(true); + } + + if (!inPlace) { + myComponent.getScrollPane().getViewport().setViewPosition(new Point(0, 0)); + JBTableWithRowHeaders.RowHeaderTable rowTable = ((JBTableWithRowHeaders)myTable).getRowHeaderTable(); + rowTable.setRowShift(0); + } + ((AsyncArrayTableModel)myTable.getModel()).fireTableDataChanged(); + ((AsyncArrayTableModel)myTable.getModel()).fireTableCellUpdated(0, 0); + if (myTable.getColumnCount() > 0) { + myTable.setDefaultRenderer(myTable.getColumnClass(0), myTableCellRenderer); + } + } + }); + } + else { + showError("Bad metadata for array " + chunk.getValue()); + } } private static String getTitlePresentation(String slice) { return "Array View: " + slice; } - private void fillColorRange(@NotNull final Runnable returnToMain) { - Consumer callback = new Consumer() { - @Override - public void consume(@NotNull PyDebugValue result) { - String rawValue = result.getValue(); - double min; - double max; - String minValue = rawValue.substring(1, rawValue.indexOf(",")); - String maxValue = rawValue.substring(rawValue.indexOf(", ") + 2, rawValue.length() - 1); - if ("c".equals(myDtypeKind)) { - min = 0; - max = 1; - myTableCellRenderer.setComplexMin(minValue); - myTableCellRenderer.setComplexMax(maxValue); - } - else if ("b".equals(myDtypeKind)) { - min = minValue.equals("True") ? 1 : 0; - max = maxValue.equals("True") ? 1 : 0; - } - else { - min = Double.parseDouble(minValue); - max = Double.parseDouble(maxValue); - } - - myTableCellRenderer.setMin(min); - myTableCellRenderer.setMax(max); - returnToMain.run(); - } - }; - - if (getMaxRow(myShape) * getMaxColumn(myShape) > HUGE_ARRAY_SIZE) { - disableColor(); - returnToMain.run(); + private void fillColorRange(String minValue, String maxValue) { + double min; + double max; + if ("c".equals(myDtypeKind)) { + min = 0; + max = 1; + myTableCellRenderer.setComplexMin(minValue); + myTableCellRenderer.setComplexMax(maxValue); } - - String evalTypeCommand = "[" + getNodeName() + ".min(), " + getNodeName() + ".max()]"; - try { - PyDebugValue value = getEvaluator().evaluate(evalTypeCommand, false, false); - callback.consume(value); - } - catch (PyDebuggerException e) { - showError(e.getMessage()); - } - } - - public String getDefaultPresentation() { - List> defaultSlice = getDefaultSlice(); - String mySlicePresentation = getNodeName(); - for (int index = 0; index < defaultSlice.size() - 2; index++) { - mySlicePresentation += "[" + defaultSlice.get(index).getFirst() + "]"; - } - - // fill current slice - final int columns = Math.min(getMaxColumn(myShape), COLUMNS_IN_DEFAULT_VIEW); - int rows = Math.min(getMaxRow(myShape), ROWS_IN_DEFAULT_VIEW); - if (rows == 1 && columns == 1) { - return mySlicePresentation; - } - - if (rows == 1) { - mySlicePresentation += "[0:" + columns + "]"; - } - else if (columns == 1) { - mySlicePresentation += "[0:" + rows + "]"; + else if ("b".equals(myDtypeKind)) { + min = minValue.equals("True") ? 1 : 0; + max = maxValue.equals("True") ? 1 : 0; } else { - mySlicePresentation += "[0:" + rows + ", 0:" + columns + "]"; + min = Double.parseDouble(minValue); + max = Double.parseDouble(maxValue); } - return mySlicePresentation; - } - - private List> getDefaultSlice() { - return getSlice(COLUMNS_IN_DEFAULT_SLICE, ROWS_IN_DEFAULT_SLICE); - } - - private List> getSlice(int columns, int rows) { - List> slices = new ArrayList>(); - for (int i = 0; i < myShape.length; i++) { - Pair slice = new Pair(0, 0); - if (i == myShape.length - 1) { - slice = new Pair(0, Math.min(myShape[i], columns)); - } - else if (i == myShape.length - 2) { - slice = new Pair(0, Math.min(myShape[i], rows)); - } - slices.add(slice); - } - return slices; + myTableCellRenderer.setMin(min); + myTableCellRenderer.setMax(max); } public String getSliceText() { - if (myComponent.getSliceTextField().getText().isEmpty()) { - return getDefaultPresentation(); - } return myComponent.getSliceTextField().getText(); } - private void fillType(@NotNull final Runnable returnToMain) { - String evalTypeCommand = getNodeName() + ".dtype.kind"; - try { - PyDebugValue value = getEvaluator().evaluate(evalTypeCommand, false, false); - setDtypeKind(value.getValue()); - returnToMain.run(); - } - catch (PyDebuggerException e) { - showError(e.getMessage()); - } - } - - private void fillShape(@NotNull final Runnable returnToMain) { - String evalShapeCommand = getEvalShapeCommand(getNodeName()); - try { - PyDebugValue value = getEvaluator().evaluate(evalShapeCommand, false, false); - setShape(parseShape(value.getValue())); - returnToMain.run(); - } - catch (Exception e) { - showError(e.getMessage()); - } - } - - private int[] parseShape(String value) throws InvalidAttributeValueException { - int index = value.indexOf('#'); - if (index == -1) { - LOG.error("Wrong shape format: " + value); - return new int[]{0, 0}; - } - String shape = value.substring(0, index); - if (shape.equals("()")) { - return new int[]{1, 1}; - } - - String[] dimensions = shape.substring(1, shape.length() - 1).trim().split(","); - if (dimensions.length > 1) { - int[] result = new int[dimensions.length]; - for (int i = 0; i < dimensions.length; i++) { - result[i] = Integer.parseInt(dimensions[i].trim()); - } - return result; - } - else if (dimensions.length == 1) { - - //special case with 1D arrays arr[i, :] - row, - //but arr[:, i] - column with equal shape and ndim - //http://stackoverflow.com/questions/16837946/numpy-a-2-rows-1-column-file-loadtxt-returns-1row-2-columns - //explanation: http://stackoverflow.com/questions/15165170/how-do-i-maintain-row-column-orientation-of-vectors-in-numpy?rq=1 - //we use kind of a hack - use information about memory from C_CONTIGUOUS - - boolean isRow = value.substring(value.indexOf("#") + 1).equals("True"); - int[] result = new int[2]; - if (isRow) { - result[0] = 1; - result[1] = Integer.parseInt(dimensions[0].trim()); - } - else { - result[1] = 1; - result[0] = Integer.parseInt(dimensions[0].trim()); - } - return result; - } - else { - throw new InvalidAttributeValueException("Invalid shape string for " + getNodeName() + "."); - } - } - public boolean isNumeric() { if (myDtypeKind != null) { return "biufc".contains(myDtypeKind.substring(0, 1)); @@ -399,7 +299,7 @@ public class NumpyArrayTable { } private void initTableModel(final boolean inPlace) { - myPagingModel = createTableModel(myShape); + myPagingModel = new AsyncArrayTableModel(myPagingModel.getRowCount(), myPagingModel.getColumnCount(), this); UIUtil.invokeLaterIfNeeded(new Runnable() { @Override @@ -419,102 +319,6 @@ public class NumpyArrayTable { }); } - private TableCellEditor getArrayTableCellEditor() { - return new ArrayTableCellEditor(myProject) { - - private String getCellSlice() { - String expression = getSliceText(); - if (myTable.getRowCount() == 1) { - expression += "[" + myTable.getSelectedColumn() + "]"; - } - else if (myTable.getColumnCount() == 1) { - expression += "[" + myTable.getSelectedRow() + "]"; - } - else { - expression += "[" + myTable.getSelectedRow() + "][" + myTable.getSelectedColumn() + "]"; - } - if (myTable.getRowCount() == 1 && myTable.getColumnCount() == 1) { - return getSliceText(); - } - return expression; - } - - private String changeValExpression() { - if (getEditor().getEditor() == null) { - throw new IllegalStateException("Null editor in table cell."); - } - - return getCellSlice() + " = " + getEditor().getEditor().getDocument().getText(); - } - - - @Override - public void cancelEditing() { - super.cancelEditing(); - clearErrorMessage(); - } - - @Override - public void doOKAction(final int row, final int col) { - - if (getEditor().getEditor() == null) { - return; - } - - myEvaluator.evaluate(changeValExpression(), new XDebuggerEvaluator.XEvaluationCallback() { - @Override - public void evaluated(@NotNull XValue result) { - XDebuggerEvaluator.XEvaluationCallback callback = new XDebuggerEvaluator.XEvaluationCallback() { - @Override - public void evaluated(@NotNull XValue value) { - - //todo: compute presentation and work with - String text = ((PyDebugValue)value).getValue(); - final String corrected; - if (!isNumeric()) { - if (!text.startsWith("\\\'") && !text.startsWith("\\\"")) { - corrected = "\'" + text + "\'"; - } - else { - corrected = text; - } - } - else { - corrected = text; - disableColor(); - } - - new WriteCommandAction(null) { - protected void run(@NotNull Result result) throws Throwable { - if (getEditor().getEditor() != null) { - getEditor().getEditor().getDocument().setText(corrected); - ((AsyncArrayTableModel)myTable.getModel()).changeValue(row, col, corrected); - cancelEditing(); - } - } - }.execute(); - setLastValue(corrected); - } - - @Override - public void errorOccurred(@NotNull String errorMessage) { - showError(errorMessage); - } - }; - - myEvaluator.evaluate(getCellSlice(), callback, null); - } - - @Override - public void errorOccurred(@NotNull String errorMessage) { - showError(errorMessage); - } - }, null); - super.doOKAction(row, col); - } - }; - } - public String correctStringValue(@NotNull Object value) { if (value instanceof String) { String corrected = (String)value; @@ -535,14 +339,6 @@ public class NumpyArrayTable { this.myDtypeKind = dtype; } - public int[] getShape() { - return myShape; - } - - public void setShape(int[] shape) { - this.myShape = shape; - } - public void showError(String message) { myDialog.setError(message); } @@ -558,80 +354,15 @@ public class NumpyArrayTable { }); } - public String getDefaultFormat() { - if (isNumeric()) { - if (myDtypeKind.equals("f")) { - return "%.5f"; - } - - if (myDtypeKind.equals("i") || myDtypeKind.equals("u")) { - return "%d"; - } - - if (myDtypeKind.equals("b") || myDtypeKind.equals("c")) { - UIUtil.invokeLaterIfNeeded(new Runnable() { - @Override - public void run() { - myComponent.getFormatTextField().getComponent().setEnabled(false); - } - }); - return "%s"; - } - } - return "%s"; - } - - public String getFormat() { - if (myComponent.getFormatTextField().getText().isEmpty()) { - return getDefaultFormat(); - } - return myComponent.getFormatTextField().getText(); - } - - private void doReslice(final String newSlice, int[] shape) { - if (shape == null) { - String evalShapeCommand = getEvalShapeCommand(newSlice); - try { - PyDebugValue result = getEvaluator().evaluate(evalShapeCommand, false, false); - shape = parseShape(((PyDebugValue)result).getValue()); - if (!is2DShape(shape)) { - showError("Incorrect slice shape " + ((PyDebugValue)result).getValue() + "."); - } - doReslice(newSlice, shape); - } - catch (Exception e) { - showError(e.getMessage()); - } - return; - } - - myShape = shape; - reset(); - } - - private static String getEvalShapeCommand(@NotNull String slice) { - //add information about memory, see #parseShape comments - return "repr(" + slice + ".shape)+'#'+repr(" + slice + ".flags['C_CONTIGUOUS'])"; + private void doReslice() { + clearErrorMessage(); + init(getSliceText(), false); } private void clearErrorMessage() { showError(null); } - private static boolean is2DShape(int[] shape) { - if (shape.length < 2) { - return false; - } - - for (int i = 0; i < shape.length - 2; i++) { - if (shape[i] != 1) { - return false; - } - } - - return true; - } - private void doApplyFormat() { reset(); } @@ -641,24 +372,6 @@ public class NumpyArrayTable { initTableModel(true); } - public String evalTypeFunc(String format) { - return "\'" + format + "\' % l"; - } - - public int getMaxRow(int[] shape) { - if (shape != null && shape.length >= 2) { - return shape[shape.length - 2]; - } - return 0; - } - - public int getMaxColumn(int[] shape) { - if (shape != null && shape.length >= 2) { - return shape[shape.length - 1]; - } - return 0; - } - public void setBusy(final boolean busy) { UIUtil.invokeLaterIfNeeded(new Runnable() { @Override @@ -719,7 +432,18 @@ public class NumpyArrayTable { } } - public String getNodeName() { - return (myValue).getName(); + public String getNodeFullName() { + String fullName = getDebugValue().getName(); + PyDebugValue child = getDebugValue(); + while (child.getParent() != null) { + child = child.getParent(); + fullName = child.getName() + "." + fullName; + } + + return fullName; + } + + public String getFormat() { + return myComponent.getFormatTextField().getText(); } } From 245b93eadfec133cca0691e6ea7c8148831a3c00 Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Sun, 16 Nov 2014 19:12:13 +0300 Subject: [PATCH 3/3] [log] IDEA-115897 clear text filter on removing the text --- .../vcs/log/ui/filter/VcsLogClassicFilterUi.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/VcsLogClassicFilterUi.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/VcsLogClassicFilterUi.java index 76b3f091eb8c..44a1d2508338 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/VcsLogClassicFilterUi.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/VcsLogClassicFilterUi.java @@ -26,6 +26,7 @@ import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.NotNullComputable; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.DocumentAdapter; import com.intellij.ui.SearchTextFieldWithStoredHistory; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; @@ -39,6 +40,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import javax.swing.event.DocumentEvent; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -226,9 +228,21 @@ public class VcsLogClassicFilterUi implements VcsLogFilterUi { textFilter.addCurrentTextToHistory(); } }); + resetFilterOnTextClear(textFilter); return textFilter; } + private void resetFilterOnTextClear(@NotNull SearchTextFieldWithStoredHistory textFilter) { + textFilter.addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(DocumentEvent e) { + if (e.getDocument().getLength() == 0) { + myFilterModel.setFilter(null); + } + } + }); + } + @Override public void actionPerformed(@NotNull AnActionEvent e) { }